from __future__ import annotations

"""Backend services for monetisation aligned with the MySQL architecture."""

import json
import time
from dataclasses import dataclass
from typing import Dict, Iterable, Optional, Sequence, Tuple

from ..database.mysql_manager import MySQLManager
from ..payment.webhook_handler import WebhookHandler


@dataclass
class SubscriptionRecord:
    """Representation of a stored subscription."""

    user_id: int
    email: str
    tier: str
    stripe_customer_id: str
    stripe_subscription_id: str
    status: str
    current_period_end: int


class SubscriptionRepository:
    """Persist subscription and token data using MySQL (with SQLite fallback for tests)."""

    def __init__(self, manager: MySQLManager):
        self.manager = manager
        self._ensure_schema_if_sqlite()

    # --- helpers -----------------------------------------------------------------
    def _normalize_query(self, query: str, params: Sequence) -> Tuple[str, Sequence]:
        if self.manager.paramstyle == "qmark":
            query = query.replace("%s", "?")
        return query, params

    def _execute(self, query: str, params: Sequence = ()) -> None:
        query, params = self._normalize_query(query, params)
        with self.manager.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(query, params)
            if self.manager.paramstyle == "qmark":
                conn.commit()

    def _fetchone(self, query: str, params: Sequence = ()):
        query, params = self._normalize_query(query, params)
        with self.manager.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(query, params)
            row = cursor.fetchone()
            if self.manager.paramstyle == "qmark":
                conn.commit()
            return row

    def _fetchall(self, query: str, params: Sequence = ()) -> Iterable:
        query, params = self._normalize_query(query, params)
        with self.manager.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(query, params)
            rows = cursor.fetchall()
            if self.manager.paramstyle == "qmark":
                conn.commit()
            return rows

    # --- schema for sqlite fallback ----------------------------------------------
    def _ensure_schema_if_sqlite(self) -> None:
        if self.manager.paramstyle != "qmark":
            return
        with self.manager.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                CREATE TABLE IF NOT EXISTS users (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    email TEXT UNIQUE NOT NULL,
                    subscription_tier TEXT DEFAULT 'FREE',
                    stripe_customer_id TEXT UNIQUE,
                    subscription_status TEXT DEFAULT 'active',
                    subscription_expires_at INTEGER
                )
                """
            )
            cursor.execute(
                """
                CREATE TABLE IF NOT EXISTS subscriptions (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    user_id INTEGER NOT NULL,
                    stripe_subscription_id TEXT UNIQUE NOT NULL,
                    stripe_product_id TEXT,
                    stripe_price_id TEXT,
                    status TEXT NOT NULL,
                    current_period_start INTEGER,
                    current_period_end INTEGER,
                metadata TEXT,
                    created_at INTEGER DEFAULT (strftime('%s','now')),
                    updated_at INTEGER DEFAULT (strftime('%s','now')),
                    FOREIGN KEY(user_id) REFERENCES users(id)
                )
                """
            )
            cursor.execute(
                """
                CREATE TABLE IF NOT EXISTS auth_tokens (
                    user_id INTEGER PRIMARY KEY,
                    token_hash TEXT NOT NULL,
                    created_at INTEGER NOT NULL,
                    FOREIGN KEY(user_id) REFERENCES users(id)
                )
                """
            )
            conn.commit()

    # --- subscription access -----------------------------------------------------
    def upsert_subscription(self, record: SubscriptionRecord) -> None:
        metadata = json.dumps({"email": record.email, "tier": record.tier})
        current_start = int(time.time())

        if self.manager.paramstyle == "qmark":
            query = """
                INSERT INTO subscriptions (
                    user_id, stripe_subscription_id, stripe_product_id, stripe_price_id,
                    status, current_period_start, current_period_end, metadata
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(stripe_subscription_id) DO UPDATE SET
                    status = excluded.status,
                    current_period_start = excluded.current_period_start,
                    current_period_end = excluded.current_period_end,
                    metadata = excluded.metadata
            """
            params = (
                record.user_id,
                record.stripe_subscription_id,
                "prod_default",
                "price_default",
                record.status,
                current_start,
                record.current_period_end,
                metadata,
            )
            self._execute(query, params)
            self._execute(
                """
                UPDATE users
                SET subscription_tier = ?,
                    stripe_customer_id = ?,
                    subscription_status = ?,
                    subscription_expires_at = ?
                WHERE id = ?
                """,
                (
                    record.tier.upper(),
                    record.stripe_customer_id,
                    record.status,
                    record.current_period_end,
                    record.user_id,
                ),
            )
        else:
            query = """
                INSERT INTO subscriptions (
                    user_id, stripe_subscription_id, stripe_product_id, stripe_price_id,
                    status, current_period_start, current_period_end, metadata
                )
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
                ON DUPLICATE KEY UPDATE
                    status = VALUES(status),
                    current_period_start = VALUES(current_period_start),
                    current_period_end = VALUES(current_period_end),
                    metadata = VALUES(metadata),
                    updated_at = NOW()
            """
            params = (
                record.user_id,
                record.stripe_subscription_id,
                "prod_default",
                "price_default",
                record.status,
                current_start,
                record.current_period_end,
                metadata,
            )
            self._execute(query, params)
            self._execute(
                """
                UPDATE users
                SET subscription_tier = %s,
                    stripe_customer_id = %s,
                    subscription_status = %s,
                    subscription_expires_at = %s
                WHERE id = %s
                """,
                (
                    record.tier.upper(),
                    record.stripe_customer_id,
                    record.status,
                    record.current_period_end,
                    record.user_id,
                ),
            )

    def get_subscription(self, user_id: int) -> Optional[SubscriptionRecord]:
        query = """
            SELECT s.user_id, u.email, u.subscription_tier, u.stripe_customer_id,
                   s.stripe_subscription_id, s.status, s.current_period_end
            FROM subscriptions s
            JOIN users u ON u.id = s.user_id
            WHERE s.user_id = %s
        """
        row = self._fetchone(query, (user_id,))
        if not row:
            return None
        idx = {k: i for i, k in enumerate(getattr(row, "keys", lambda: [])())} if hasattr(row, "keys") else None

        def _get(value):
            if isinstance(row, tuple):
                return row[idx[value]]
            return row[value]

        return SubscriptionRecord(
            user_id=_get("user_id"),
            email=_get("email"),
            tier=_get("subscription_tier"),
            stripe_customer_id=_get("stripe_customer_id"),
            stripe_subscription_id=_get("stripe_subscription_id"),
            status=_get("status"),
            current_period_end=_get("current_period_end"),
        )

    def list_active(self) -> Iterable[SubscriptionRecord]:
        query = """
            SELECT s.user_id, u.email, u.subscription_tier, u.stripe_customer_id,
                   s.stripe_subscription_id, s.status, s.current_period_end
            FROM subscriptions s
            JOIN users u ON u.id = s.user_id
            WHERE s.status = 'active'
            ORDER BY s.current_period_end DESC
        """
        rows = self._fetchall(query)
        for row in rows:
            if hasattr(row, "keys"):
                data = {k: row[k] for k in row.keys()}
            else:
                data = row
            yield SubscriptionRecord(
                user_id=data["user_id"],
                email=data["email"],
                tier=data["subscription_tier"],
                stripe_customer_id=data["stripe_customer_id"],
                stripe_subscription_id=data["stripe_subscription_id"],
                status=data["status"],
                current_period_end=data["current_period_end"],
            )

    def store_token(self, user_id: int, token_hash: str, created_at: int) -> None:
        query = """
            INSERT INTO auth_tokens (user_id, token_hash, created_at)
            VALUES (%s, %s, %s)
            ON CONFLICT(user_id) DO UPDATE SET token_hash = excluded.token_hash,
                created_at = excluded.created_at
        """
        if self.manager.paramstyle != "qmark":
            query = query.replace(
                "ON CONFLICT(user_id) DO UPDATE SET token_hash = excluded.token_hash,\n                created_at = excluded.created_at",
                "ON DUPLICATE KEY UPDATE token_hash = VALUES(token_hash), created_at = VALUES(created_at)",
            )
        self._execute(query, (user_id, token_hash, created_at))

    def get_token_hash(self, user_id: int) -> Optional[str]:
        row = self._fetchone("SELECT token_hash FROM auth_tokens WHERE user_id = %s", (user_id,))
        if not row:
            return None
        if hasattr(row, "keys"):
            return row["token_hash"]
        return row[0]


class SubscriptionBackend:
    """Handle Stripe webhook callbacks and update the repository."""

    def __init__(self, repository: SubscriptionRepository, webhook_secret: str):
        self.repository = repository
        self.webhook_secret = webhook_secret
        self.webhook_handler = WebhookHandler()

    def handle_webhook(self, payload: str, signature_header: str) -> bool:
        if not self.webhook_handler.validate_signature(
            payload, signature_header, self.webhook_secret
        ):
            return False

        event = json.loads(payload)
        event_type = event.get("type", "")
        data_object = event.get("data", {}).get("object", {})

        if event_type == "checkout.session.completed":
            self._handle_checkout_session(data_object)
            return True

        if event_type in {"customer.subscription.updated", "customer.subscription.deleted"}:
            self._handle_subscription_event(data_object)
            return True

        return False

    def _handle_checkout_session(self, obj: Dict) -> None:
        metadata = obj.get("metadata", {})
        user_id = metadata.get("user_id")
        if not user_id:
            return
        try:
            user_id_int = int(user_id)
        except ValueError:
            return

        tier = metadata.get("tier", "FREE")
        email = obj.get("customer_details", {}).get("email") or metadata.get("email", "")
        customer_id = obj.get("customer", "")
        subscription_id = obj.get("subscription", "")
        current_period_end = int(obj.get("subscription_period_end", time.time()))

        record = SubscriptionRecord(
            user_id=user_id_int,
            email=email,
            tier=tier,
            stripe_customer_id=customer_id,
            stripe_subscription_id=subscription_id,
            status="active",
            current_period_end=current_period_end,
        )
        self.repository.upsert_subscription(record)

    def _handle_subscription_event(self, obj: Dict) -> None:
        metadata = obj.get("metadata", {})
        user_id = metadata.get("user_id")
        if not user_id:
            return
        try:
            user_id_int = int(user_id)
        except ValueError:
            return

        record = SubscriptionRecord(
            user_id=user_id_int,
            email=metadata.get("email", ""),
            tier=metadata.get("tier", "FREE"),
            stripe_customer_id=obj.get("customer", ""),
            stripe_subscription_id=obj.get("id", ""),
            status=obj.get("status", "active"),
            current_period_end=int(obj.get("current_period_end", time.time())),
        )
        self.repository.upsert_subscription(record)


class AuthenticationService:
    """Issue and validate API tokens for authenticated users."""

    def __init__(self, repository: SubscriptionRepository, secret: str = "web-sentinel-auth"):
        self.repository = repository
        self.secret = secret

    @staticmethod
    def _hash_token(secret: str, token: str) -> str:
        import hashlib

        return hashlib.sha256(f"{secret}:{token}".encode("utf-8")).hexdigest()

    def issue_token(self, user_id: int) -> str:
        import secrets

        token = secrets.token_urlsafe(32)
        token_hash = self._hash_token(self.secret, token)
        self.repository.store_token(user_id, token_hash, int(time.time()))
        return token

    def validate_token(self, user_id: int, token: str) -> bool:
        stored_hash = self.repository.get_token_hash(user_id)
        if not stored_hash:
            return False
        return stored_hash == self._hash_token(self.secret, token)
