"""
license.py — License key activation against Supabase REST API.

Storage priority:
  1. OS keychain via keyring
  2. Encrypted local file (machine-ID derived key)
  3. SICHGATE_LICENSE_KEY env var (CI/CD)

Validates online once per 24 hours. Offline grace period: 7 days.
"""

from __future__ import annotations

import hashlib
import json
import logging
import os
import platform
import uuid
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional, Tuple

import httpx

logger = logging.getLogger(__name__)

_KEYRING_SERVICE    = "sichgate-pro"
_KEYRING_USERNAME   = "license-key"
_CONFIG_PATH        = Path.home() / ".sichgate" / "config.json"
_ENV_VAR            = "SICHGATE_LICENSE_KEY"

# Supabase credentials — loaded from _constants.py (gitignored)
from sichgate._constants import SUPABASE_URL as _SUPABASE_URL, SUPABASE_ANON_KEY as _SUPABASE_ANON_KEY


class LicenseError(Exception):
    pass


class LicenseManager:

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def activate(self, key: str) -> Tuple[bool, str]:
        """Validate key against Supabase, store locally. Returns (ok, message)."""
        key = key.strip().upper()
        try:
            data = self._fetch_license(key)
        except Exception as exc:
            return False, str(exc)

        if data["status"] != "active":
            return False, f"License is {data['status']}. Contact support@sichgate.com"

        expires = datetime.fromisoformat(data["expires_at"].replace("Z", "+00:00"))
        if expires < datetime.now(timezone.utc):
            return False, "License expired. Renew at sichgate.com"

        config = {
            "license_key":        key,
            "tier":               data["tier"],
            "expires_at":         data["expires_at"],
            "scan_limit":         data["scan_limit"],
            "validated_at":       datetime.now(timezone.utc).isoformat(),
            "machine_fingerprint": self._machine_id(),
        }
        self._write_config(config)
        self._update_activated_at(key)

        return True, f"License active — {data['tier'].title()} tier · expires {expires.strftime('%Y-%m-%d')}"

    def deactivate(self) -> Tuple[bool, str]:
        """Remove local license config."""
        if _CONFIG_PATH.exists():
            _CONFIG_PATH.unlink()
        try:
            import keyring
            keyring.delete_password(_KEYRING_SERVICE, _KEYRING_USERNAME)
        except Exception:
            pass
        return True, "License removed from this machine."

    def check(self) -> Tuple[bool, str]:
        """
        Called on every sichgate invocation.
        Uses cached config if validated within 24h.
        Re-validates online if stale. Offline grace: 7 days.
        """
        config = self._read_config()
        if not config:
            return False, "No license found. Run: sichgate activate"

        validated_at = datetime.fromisoformat(config["validated_at"])
        age = datetime.now(timezone.utc) - validated_at.replace(tzinfo=timezone.utc)

        if age < timedelta(hours=24):
            return True, config["tier"]  # cache hit

        # Re-validate online
        try:
            data = self._fetch_license(config["license_key"])
            config["validated_at"] = datetime.now(timezone.utc).isoformat()
            config["tier"]         = data["tier"]
            config["expires_at"]   = data["expires_at"]
            config["scan_limit"]   = data["scan_limit"]
            self._write_config(config)
            return True, config["tier"]
        except Exception:
            # Offline grace period — 7 days
            if age < timedelta(days=7):
                return True, config["tier"]
            return False, "License check failed and grace period expired. Connect to internet."

    def is_activated(self) -> bool:
        ok, _ = self.check()
        return ok

    def get_tier(self) -> str:
        _, tier = self.check()
        return tier

    # ------------------------------------------------------------------
    # Supabase REST
    # ------------------------------------------------------------------

    def _fetch_license(self, key: str) -> dict:
        resp = httpx.get(
            f"{_SUPABASE_URL}/rest/v1/licenses",
            params={"key": f"eq.{key}", "select": "*"},
            headers={
                "apikey":        _SUPABASE_ANON_KEY,
                "Authorization": f"Bearer {_SUPABASE_ANON_KEY}",
            },
            timeout=10,
        )
        if resp.status_code != 200:
            raise LicenseError(f"License server error ({resp.status_code}). Try again.")
        rows = resp.json()
        if not rows:
            raise LicenseError("Invalid license key.")
        return rows[0]

    def _update_activated_at(self, key: str) -> None:
        """Best-effort — write activated_at timestamp to DB."""
        try:
            httpx.patch(
                f"{_SUPABASE_URL}/rest/v1/licenses",
                params={"key": f"eq.{key}"},
                headers={
                    "apikey":        _SUPABASE_ANON_KEY,
                    "Authorization": f"Bearer {_SUPABASE_ANON_KEY}",
                    "Content-Type":  "application/json",
                    "Prefer":        "return=minimal",
                },
                json={
                    "activated_at":       datetime.now(timezone.utc).isoformat(),
                    "machine_fingerprint": self._machine_id(),
                },
                timeout=10,
            )
        except Exception:
            pass  # non-fatal

    # ------------------------------------------------------------------
    # Local config storage
    # ------------------------------------------------------------------

    def _write_config(self, config: dict) -> None:
        _CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
        _CONFIG_PATH.write_text(json.dumps(config, indent=2))

    def _read_config(self) -> Optional[dict]:
        # Env var override for CI/CD
        env_key = os.environ.get(_ENV_VAR)
        if env_key:
            try:
                data = self._fetch_license(env_key)
                return {
                    "license_key":  env_key,
                    "tier":         data["tier"],
                    "expires_at":   data["expires_at"],
                    "scan_limit":   data["scan_limit"],
                    "validated_at": datetime.now(timezone.utc).isoformat(),
                    "machine_fingerprint": self._machine_id(),
                }
            except Exception:
                return None

        if not _CONFIG_PATH.exists():
            return None
        try:
            return json.loads(_CONFIG_PATH.read_text())
        except Exception:
            return None

    # ------------------------------------------------------------------
    # Machine fingerprint
    # ------------------------------------------------------------------

    def _machine_id(self) -> str:
        raw = "|".join([
            platform.node(),
            platform.machine(),
            platform.processor(),
            str(uuid.getnode()),
        ])
        return hashlib.sha256(raw.encode()).hexdigest()[:32]