from __future__ import annotations

from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import hashlib
from pathlib import Path
import secrets
import sqlite3

from app.core.config import get_settings

DEFAULT_STAFF_PERMISSIONS = ("prenotazioni", "menu", "homemade")


def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


def _normalize_lookup(value: str) -> str:
    return value.strip().lower()


def _verify_password(password: str, stored_hash: str) -> bool:
    try:
        algorithm, raw_iterations, salt_hex, digest_hex = stored_hash.split("$", 3)
    except ValueError:
        return False

    if algorithm != "pbkdf2_sha256":
        return False

    try:
        iterations = int(raw_iterations)
        salt = bytes.fromhex(salt_hex)
        expected_digest = bytes.fromhex(digest_hex)
    except ValueError:
        return False

    candidate = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations)
    return secrets.compare_digest(candidate, expected_digest)


@dataclass(frozen=True)
class AuthContext:
    token: str
    tenant_id: str
    tenant_slug: str
    tenant_name: str
    user_id: str
    user_email: str
    username: str | None
    user_name: str | None
    role: str
    permissions: tuple[str, ...]
    database_url: str
    default_staff: str
    source: str


@contextmanager
def _connect_registry():
    settings = get_settings()
    registry_path = Path(settings.tenancy_registry_database)
    if not registry_path.exists():
        yield None
        return

    connection = sqlite3.connect(registry_path)
    connection.row_factory = sqlite3.Row
    try:
        yield connection
    finally:
        connection.close()


def _tenant_database_url(path: str) -> str:
    return f"sqlite:///{Path(path)}"


def _build_legacy_context(token: str) -> AuthContext:
    settings = get_settings()
    return AuthContext(
        token=token,
        tenant_id="legacy_ordini",
        tenant_slug="legacy-ordini",
        tenant_name="Ordini Legacy",
        user_id="legacy_admin",
        user_email="legacy@ordini.local",
        username="legacy_admin",
        user_name=settings.default_staff,
        role="owner",
        permissions=("ordini", "prenotazioni", "documents", "menu", "homemade", "fiscal_documents"),
        database_url=settings.database_url,
        default_staff=settings.default_staff,
        source="legacy",
    )


def _row_to_context(row: sqlite3.Row, token: str) -> AuthContext:
    default_staff = (row["user_name"] or row["username"] or row["tenant_name"] or get_settings().default_staff).strip()
    permissions_json = row["permissions_json"] if "permissions_json" in row.keys() else None
    permissions: tuple[str, ...] = tuple()
    if row["role"] in {"owner", "super_admin"}:
        permissions = ("ordini", "prenotazioni", "documents", "menu", "homemade", "fiscal_documents")
    elif permissions_json is None:
        permissions = DEFAULT_STAFF_PERMISSIONS if row["role"] == "staff" else tuple()
    else:
        try:
            import json

            parsed = json.loads(permissions_json)
        except Exception:
            parsed = None
        if isinstance(parsed, list):
            normalized: list[str] = []
            for item in parsed:
                value = str(item or "").strip().lower()
                if not value or value in normalized:
                    continue
                normalized.append(value)
            permissions = tuple(normalized)

    return AuthContext(
        token=token,
        tenant_id=row["tenant_id"],
        tenant_slug=row["tenant_slug"],
        tenant_name=row["tenant_name"],
        user_id=row["user_id"],
        user_email=row["user_email"],
        username=row["username"],
        user_name=row["user_name"],
        role=row["role"],
        permissions=permissions,
        database_url=_tenant_database_url(row["database_path"]),
        default_staff=default_staff or get_settings().default_staff,
        source="tenant",
    )


def _resolve_session_from_registry(connection: sqlite3.Connection, token: str) -> AuthContext | None:
    row = connection.execute(
        """
        SELECT
            sessions.token,
            sessions.tenant_id,
            sessions.user_id,
            sessions.expires_at,
            tenants.slug AS tenant_slug,
            tenants.name AS tenant_name,
            tenants.database_path AS database_path,
            users.email AS user_email,
            users.username AS username,
            users.name AS user_name,
            users.role AS role,
            users.permissions_json AS permissions_json
        FROM sessions
        JOIN tenants ON tenants.id = sessions.tenant_id
        JOIN users ON users.id = sessions.user_id
        WHERE sessions.token = ?
        LIMIT 1
        """,
        (token,),
    ).fetchone()

    if row is None:
        return None

    expires_at = datetime.fromisoformat(row["expires_at"])
    if expires_at < _utcnow():
        connection.execute("DELETE FROM sessions WHERE token = ?", (token,))
        connection.commit()
        return None

    return _row_to_context(row, token)


def resolve_token(token: str) -> AuthContext | None:
    settings = get_settings()
    if settings.api_token and token == settings.api_token:
        return _build_legacy_context(token)

    with _connect_registry() as connection:
        if connection is None:
            return None

        return _resolve_session_from_registry(connection, token)


def authenticate_login(*, identifier: str | None = None, password: str | None = None, session_token: str | None = None) -> AuthContext:
    settings = get_settings()

    if session_token:
        context = resolve_token(session_token.strip())
        if context is not None:
            return context
        raise ValueError("Sessione gestionale non valida.")

    normalized_identifier = (identifier or "").strip()
    normalized_password = (password or "").strip()
    if normalized_identifier and normalized_password:
        with _connect_registry() as connection:
            if connection is not None:
                user = connection.execute(
                    """
                    SELECT
                        users.id AS user_id,
                        users.tenant_id AS tenant_id,
                        users.email AS user_email,
                        users.username AS username,
                        users.name AS user_name,
                        users.role AS role,
                        users.permissions_json AS permissions_json,
                        users.password_hash AS password_hash,
                        tenants.slug AS tenant_slug,
                        tenants.name AS tenant_name,
                        tenants.database_path AS database_path
                    FROM users
                    JOIN tenants ON tenants.id = users.tenant_id
                    WHERE lower(users.email) = ? OR lower(users.username) = ?
                    LIMIT 1
                    """,
                    (_normalize_lookup(normalized_identifier), _normalize_lookup(normalized_identifier)),
                ).fetchone()

                if user is not None and _verify_password(normalized_password, user["password_hash"]):
                    token = secrets.token_urlsafe(32)
                    created_at = _utcnow().isoformat()
                    expires_at = (_utcnow() + timedelta(hours=settings.tenancy_session_duration_hours)).isoformat()
                    connection.execute(
                        """
                        INSERT INTO sessions (token, tenant_id, user_id, created_at, expires_at)
                        VALUES (?, ?, ?, ?, ?)
                        """,
                        (token, user["tenant_id"], user["user_id"], created_at, expires_at),
                    )
                    connection.commit()
                    return _row_to_context(user, token)

        raise ValueError("Credenziali non valide.")

    raise ValueError("Inserisci le credenziali del gestionale oppure una sessione valida.")
