from __future__ import annotations

import base64
import hashlib
import json
from dataclasses import dataclass
from datetime import date, timedelta
from pathlib import Path
from typing import Any

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

from .device_fingerprint import get_device_code
from .license_storage import read_json, write_json
from .public_key import PUBLIC_KEY_PEM

TRIAL_DAYS = 60
TRIAL_SECRET = 'mi100-trial-v1-local-salt'


@dataclass
class LicenseStatus:
    mode: str  # full/trial/expired/invalid
    valid: bool
    device_code: str
    customer_name: str = ''
    company_name: str = ''
    issued_at: str = ''
    expires_at: str = ''
    days_left: int = 0
    message: str = ''
    warning_level: str = ''
    license_path: str = ''


class LicenseManager:
    def __init__(self, license_dir: Path):
        self.license_dir = Path(license_dir)
        self.license_dir.mkdir(parents=True, exist_ok=True)
        self.license_path = self.license_dir / 'license.dat'
        self.trial_path = self.license_dir / 'trial.dat'
        self.device_code = get_device_code()
        self._public_key = serialization.load_pem_public_key(PUBLIC_KEY_PEM.encode('utf-8'))

    def _trial_signature(self, payload: dict[str, Any]) -> str:
        base = json.dumps({k: payload[k] for k in sorted(payload) if k != 'signature'}, ensure_ascii=False, separators=(',', ':'))
        return hashlib.sha256((base + '|' + TRIAL_SECRET).encode('utf-8')).hexdigest()

    def ensure_trial(self) -> dict[str, Any]:
        data = read_json(self.trial_path)
        if data and self._is_valid_trial_payload(data):
            return data
        today = date.today()
        payload = {
            'product': 'Mi100 Studio Manager',
            'trial_version': 1,
            'device_code': self.device_code,
            'first_run_at': today.isoformat(),
            'expires_at': (today + timedelta(days=TRIAL_DAYS)).isoformat(),
        }
        payload['signature'] = self._trial_signature(payload)
        write_json(self.trial_path, payload)
        return payload

    def _is_valid_trial_payload(self, data: dict[str, Any]) -> bool:
        try:
            if data.get('product') != 'Mi100 Studio Manager':
                return False
            if data.get('device_code') != self.device_code:
                return False
            if data.get('signature') != self._trial_signature(data):
                return False
            return True
        except Exception:
            return False

    def verify_full_license(self, payload: dict[str, Any]) -> LicenseStatus:
        try:
            sig_b64 = str(payload.get('signature') or '')
            body = {k: payload[k] for k in payload.keys() if k != 'signature'}
            message = json.dumps(body, ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode('utf-8')
            signature = base64.b64decode(sig_b64.encode('ascii'))
            assert isinstance(self._public_key, Ed25519PublicKey)
            self._public_key.verify(signature, message)
            if body.get('product') != 'Mi100 Studio Manager':
                raise ValueError('wrong product')
            if body.get('license_type') != 'full':
                raise ValueError('wrong type')
            if body.get('device_code') != self.device_code:
                raise ValueError('device mismatch')
            return LicenseStatus(
                mode='full', valid=True, device_code=self.device_code,
                customer_name=str(body.get('customer_name') or ''),
                company_name=str(body.get('company_name') or ''),
                issued_at=str(body.get('issued_at') or ''),
                expires_at='', days_left=0, message='Ліцензія активована',
                license_path=str(self.license_path),
            )
        except Exception as e:
            return LicenseStatus(mode='invalid', valid=False, device_code=self.device_code, message=f'Невалідна ліцензія: {e}', license_path=str(self.license_path))

    def install_full_license_from_file(self, source: str | Path) -> LicenseStatus:
        src = Path(source)
        payload = read_json(src)
        if not payload:
            return LicenseStatus(mode='invalid', valid=False, device_code=self.device_code, message='Не вдалося прочитати файл ліцензії', license_path=str(self.license_path))
        status = self.verify_full_license(payload)
        if status.valid:
            self.license_dir.mkdir(parents=True, exist_ok=True)
            self.license_path.write_text(src.read_text(encoding='utf-8'), encoding='utf-8')
        return status

    def _trial_message(self, days_left: int) -> tuple[str, str]:
        if days_left < 0:
            return 'Пробний період завершено', 'expired'
        if days_left == 0:
            return 'У вас залишився останній день пробної версії програми', 'critical'
        if days_left in (1, 2, 3):
            return f'У вас залишилось {days_left} дні(в) пробної версії програми', 'critical'
        if days_left == 7:
            return 'У вас залишилось 7 днів пробної версії програми', 'warning'
        if days_left == 14:
            return 'У вас залишилось 14 днів пробної версії програми', 'warning'
        return f'У вас залишилось {days_left} днів пробної версії програми', ''

    def current_status(self) -> LicenseStatus:
        full = read_json(self.license_path)
        if full:
            st = self.verify_full_license(full)
            if st.valid:
                return st
        trial = self.ensure_trial()
        expires = date.fromisoformat(str(trial['expires_at']))
        days_left = (expires - date.today()).days
        message, warning_level = self._trial_message(days_left)
        if days_left >= 0:
            return LicenseStatus(
                mode='trial', valid=True, device_code=self.device_code,
                issued_at=str(trial.get('first_run_at') or ''),
                expires_at=str(trial.get('expires_at') or ''),
                days_left=days_left, message=message, warning_level=warning_level,
                license_path=str(self.trial_path),
            )
        return LicenseStatus(
            mode='expired', valid=False, device_code=self.device_code,
            issued_at=str(trial.get('first_run_at') or ''),
            expires_at=str(trial.get('expires_at') or ''),
            days_left=days_left, message=message, warning_level='expired',
            license_path=str(self.license_path),
        )
