"""
Credential storage service with Fernet encryption.

Manages CustomerSite and SiteCredential records with encrypted password
storage. All passwords are encrypted at rest and decrypted only when
explicitly revealed by the user.
"""

from __future__ import annotations

import json
import logging
from typing import List, Optional

from cryptography.fernet import Fernet, InvalidToken

from ..config import Config
from ..models import CustomerSite, SiteCredential

logger = logging.getLogger(__name__)

# Lazy-init Fernet cipher from env key
_fernet = None


def _get_fernet() -> Optional[Fernet]:
    """Get or initialize the Fernet cipher."""
    global _fernet
    if _fernet is not None:
        return _fernet
    key = Config.CREDENTIAL_ENCRYPTION_KEY
    if not key:
        logger.warning("CREDENTIAL_ENCRYPTION_KEY not set — passwords stored as plaintext")
        return None
    try:
        _fernet = Fernet(key.encode() if isinstance(key, str) else key)
        return _fernet
    except Exception as e:
        logger.error("Invalid CREDENTIAL_ENCRYPTION_KEY: %s", e)
        return None


def encrypt_password(plain: str) -> str:
    """Encrypt a password string. Falls back to plaintext if no key configured."""
    f = _get_fernet()
    if f is None:
        return plain
    return f.encrypt(plain.encode()).decode()


def decrypt_password(encrypted: str) -> str:
    """Decrypt a password string. Returns as-is if decryption fails."""
    f = _get_fernet()
    if f is None:
        return encrypted
    try:
        return f.decrypt(encrypted.encode()).decode()
    except InvalidToken:
        # May be a legacy plaintext password
        return encrypted


def get_customer_sites(session, customer_hash: str) -> List[dict]:
    """Get all sites and credentials for a customer (passwords masked)."""
    sites = (
        session.query(CustomerSite)
        .filter_by(customer_hash=customer_hash)
        .order_by(CustomerSite.label)
        .all()
    )
    result = []
    for site in sites:
        creds = []
        for c in site.credentials:
            creds.append({
                "id": c.id,
                "type": c.type,
                "label": c.label,
                "username": c.username,
                "has_password": bool(c.password_encrypted),
                "extra": json.loads(c.extra_json) if c.extra_json else {},
                "created_at": c.created_at.isoformat() if c.created_at else None,
            })
        result.append({
            "id": site.id,
            "label": site.label,
            "url": site.url,
            "notes": site.notes,
            "credentials": creds,
            "created_at": site.created_at.isoformat() if site.created_at else None,
        })
    return result


def add_site(session, customer_hash: str, label: str, url: Optional[str] = None,
             notes: Optional[str] = None) -> CustomerSite:
    """Create a new site for a customer."""
    site = CustomerSite(
        customer_hash=customer_hash,
        label=label,
        url=url,
        notes=notes,
    )
    session.add(site)
    session.flush()
    return site


def update_site(session, site_id: int, **fields) -> Optional[CustomerSite]:
    """Update a site's fields."""
    site = session.query(CustomerSite).get(site_id)
    if not site:
        return None
    for key in ("label", "url", "notes"):
        if key in fields:
            setattr(site, key, fields[key])
    return site


def delete_site(session, site_id: int) -> bool:
    """Delete a site and all its credentials."""
    site = session.query(CustomerSite).get(site_id)
    if not site:
        return False
    session.delete(site)
    return True


def add_credential(session, site_id: int, cred_type: str, username: Optional[str] = None,
                   password: Optional[str] = None, label: Optional[str] = None,
                   extra: Optional[dict] = None) -> Optional[SiteCredential]:
    """Add a credential to a site."""
    site = session.query(CustomerSite).get(site_id)
    if not site:
        return None
    cred = SiteCredential(
        site_id=site_id,
        type=cred_type,
        label=label,
        username=username,
        password_encrypted=encrypt_password(password) if password else None,
        extra_json=json.dumps(extra) if extra else None,
    )
    session.add(cred)
    session.flush()
    return cred


def update_credential(session, cred_id: int, **fields) -> Optional[SiteCredential]:
    """Update a credential's fields."""
    cred = session.query(SiteCredential).get(cred_id)
    if not cred:
        return None
    for key in ("type", "label", "username"):
        if key in fields:
            setattr(cred, key, fields[key])
    if "password" in fields and fields["password"]:
        cred.password_encrypted = encrypt_password(fields["password"])
    if "extra" in fields:
        cred.extra_json = json.dumps(fields["extra"]) if fields["extra"] else None
    return cred


def delete_credential(session, cred_id: int) -> bool:
    """Delete a single credential."""
    cred = session.query(SiteCredential).get(cred_id)
    if not cred:
        return False
    session.delete(cred)
    return True


def reveal_password(session, cred_id: int) -> Optional[str]:
    """Decrypt and return a credential's password."""
    cred = session.query(SiteCredential).get(cred_id)
    if not cred or not cred.password_encrypted:
        return None
    return decrypt_password(cred.password_encrypted)
