from __future__ import annotations

import hashlib
import secrets

from sqlalchemy import select

from app.db import SessionLocal, UserRow


def hash_password(password: str) -> str:
    salt = secrets.token_hex(16)
    digest = hashlib.sha256(f"{salt}{password}".encode("utf-8")).hexdigest()
    return f"{salt}${digest}"


def verify_password(password: str, password_hash: str) -> bool:
    try:
        salt, digest = password_hash.split("$", 1)
    except ValueError:
        return False
    check = hashlib.sha256(f"{salt}{password}".encode("utf-8")).hexdigest()
    return secrets.compare_digest(check, digest)


def create_user(
    *,
    platform: str,
    email: str,
    password: str,
    name: str,
    company: str | None = None,
    client_id: str | None = None,
) -> UserRow:
    email_n = email.strip().lower()
    with SessionLocal() as session:
        existing = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if existing:
            raise ValueError("An account with this email already exists")
        row = UserRow(
            platform=platform,
            email=email_n,
            password_hash=hash_password(password),
            name=name.strip(),
            company=(company or "").strip() or None,
            client_id=client_id if platform == "client" else None,
        )
        session.add(row)
        session.commit()
        session.refresh(row)
        session.expunge(row)
        return row


def authenticate_user(platform: str, email: str, password: str) -> UserRow | None:
    email_n = email.strip().lower()
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if not row or not verify_password(password, row.password_hash):
            return None
        session.expunge(row)
        return row


def get_client_portal_user(*, client_id: str, email: str | None = None) -> UserRow | None:
    """Find the Client-portal user linked to a ledger client, if one exists."""
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == "client", UserRow.client_id == client_id)
        )
        if not row and email:
            email_n = email.strip().lower()
            if email_n:
                row = session.scalar(
                    select(UserRow).where(UserRow.platform == "client", UserRow.email == email_n)
                )
        if not row:
            return None
        session.expunge(row)
        return row


def get_user(platform: str, email: str) -> UserRow | None:
    email_n = email.strip().lower()
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if not row:
            return None
        session.expunge(row)
        return row


def update_user_profile(
    platform: str,
    email: str,
    *,
    name: str,
    company: str | None = None,
) -> UserRow:
    email_n = email.strip().lower()
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if not row:
            raise ValueError("Account not found")
        row.name = name.strip()
        row.company = (company or "").strip() or None
        session.commit()
        session.refresh(row)
        session.expunge(row)
        return row


def update_user_password(platform: str, email: str, new_password: str) -> None:
    email_n = email.strip().lower()
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if not row:
            raise ValueError("Account not found")
        row.password_hash = hash_password(new_password)
        session.commit()


def ensure_user(
    *,
    platform: str,
    email: str,
    name: str,
    company: str | None = None,
    client_id: str | None = None,
) -> UserRow:
    """Get existing user or create a stub row (used for demo account avatar persistence)."""
    email_n = email.strip().lower()
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if row:
            session.expunge(row)
            return row
        sibling = session.scalar(select(UserRow).where(UserRow.email == email_n).limit(1))
        shared_avatar = sibling.avatar_url if sibling else None
        row = UserRow(
            platform=platform,
            email=email_n,
            password_hash=hash_password(secrets.token_urlsafe(24)),
            name=name.strip() or email_n,
            company=(company or "").strip() or None,
            client_id=client_id if platform == "client" else None,
            avatar_url=shared_avatar,
        )
        session.add(row)
        session.commit()
        session.refresh(row)
        session.expunge(row)
        return row


def set_avatar_url(platform: str, email: str, avatar_url: str | None) -> UserRow:
    """Set avatar for this account, and sync to the same email on the other platform."""
    email_n = email.strip().lower()
    with SessionLocal() as session:
        rows = list(session.scalars(select(UserRow).where(UserRow.email == email_n)).all())
        primary = next((r for r in rows if r.platform == platform), None)
        if not primary:
            raise ValueError("Account not found")
        for row in rows:
            row.avatar_url = avatar_url
        session.commit()
        session.refresh(primary)
        session.expunge(primary)
        return primary


def get_avatar_url(platform: str, email: str) -> str | None:
    row = get_user(platform, email)
    return row.avatar_url if row else None


def set_client_id(platform: str, email: str, client_id: str) -> UserRow:
    email_n = email.strip().lower()
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if not row:
            raise ValueError("Account not found")
        row.client_id = client_id
        session.commit()
        session.refresh(row)
        session.expunge(row)
        return row


def create_firm_managed_client(
    *,
    full_name: str,
    company_name: str,
    portal_email: str,
    password: str,
    kvk: str = "",
) -> dict:
    """Create a ledger client and Client Portal login together. Rolls back the client on user failure."""
    from app.store import store

    email_n = portal_email.strip().lower()
    name_n = full_name.strip()
    company_n = company_name.strip()
    if not name_n:
        raise ValueError("Full name is required")
    if "@" not in email_n:
        raise ValueError("Please enter a valid client email")
    if len(password) < 8:
        raise ValueError("Password must be at least 8 characters")

    if get_user("client", email_n):
        raise ValueError("An account with this email already exists")

    existing = store.find_client_by_email(email_n)
    created_new = False
    if existing:
        if get_client_portal_user(client_id=existing.id, email=email_n):
            raise ValueError("A client with this email already exists")
        client = existing
        client.name = company_n or name_n
        if kvk.strip():
            client.kvk = kvk.strip()
        client.email = email_n
        store._persist()  # noqa: SLF001
    else:
        client = store.create_client(
            company_n or name_n,
            kvk=kvk,
            email=email_n,
            link_user=False,
        )
        created_new = True

    try:
        user = create_user(
            platform="client",
            email=email_n,
            password=password,
            name=name_n,
            company=company_n or None,
            client_id=client.id,
        )
    except Exception:
        if created_new:
            store.remove_client(client.id)
        raise

    return {
        **client.model_dump(),
        "portal": {
            "name": user.name,
            "email": user.email,
            "company": user.company,
            "status": "ready",
        },
    }


def provision_client_account(platform: str, email: str) -> UserRow | None:
    """Link a client-portal user to the matching ledger client (by email), or create one."""
    if platform != "client":
        return get_user(platform, email)

    from app.store import store

    email_n = email.strip().lower()
    with SessionLocal() as session:
        row = session.scalar(
            select(UserRow).where(UserRow.platform == platform, UserRow.email == email_n)
        )
        if not row:
            return None

        if row.client_id and store.get_client(row.client_id):
            client = store.get_client(row.client_id)
            if client and not (client.email or "").strip():
                client.email = email_n
                store._persist()  # noqa: SLF001
            session.expunge(row)
            return row

        existing = store.find_client_by_email(email_n)
        if existing:
            row.client_id = existing.id
            session.commit()
            session.refresh(row)
            session.expunge(row)
            return row

        client_name = (row.company or row.name or email_n).strip() or "Client"
        client = store.create_client(client_name, email=email_n, link_user=False)
        row.client_id = client.id
        row.client_id = client.id
        session.commit()
        session.refresh(row)
        session.expunge(row)
        return row
