from __future__ import annotations

from dataclasses import dataclass
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.modules.clients.models import Client, ClientBusinessUnit, ClientBranch
from app.modules.registrations.models import ClientRegistration, RegistrationType
from app.modules.services.models import ClientServicePlan, ServiceCatalogue

VALID_SCOPE_TYPES = {"client", "business_unit", "client_branch", "registration"}
VALID_REGISTRATION_TYPES = {"GST", "TAN", "PF", "ESI", "PT", "IEC", "FSSAI", "UDYAM", "OTHER"}


@dataclass
class ScopeTarget:
    token: str
    scope_type: str
    scope_key: str
    client_id: int
    business_unit_id: int | None
    client_branch_id: int | None
    registration_id: int | None
    client_code: str
    client_name: str
    pan: str
    business_unit: str
    client_branch: str
    registration_type: str
    registration_number: str
    trade_name: str
    state: str
    entity_type: str
    partner_id: int | None
    existing_plan_status: str = ""


def normalize_scope_type(value: str | None) -> str:
    value = (value or "client").strip().lower().replace("-", "_").replace(" ", "_")
    return value if value in VALID_SCOPE_TYPES else "client"


def normalize_registration_type(value: str | None) -> str | None:
    value = (value or "").strip().upper()
    return value if value in VALID_REGISTRATION_TYPES else None


def scope_key(scope_type: str, target_id: int) -> str:
    prefixes = {
        "client": "CLIENT",
        "business_unit": "BUSINESS",
        "client_branch": "BRANCH",
        "registration": "REGISTRATION",
    }
    return f"{prefixes[scope_type]}:{int(target_id)}"


def _plan_status_map(db: Session, tenant_id: int, service_catalogue_id: int) -> dict[str, str]:
    rows = db.execute(
        select(ClientServicePlan.scope_key, ClientServicePlan.status).where(
            ClientServicePlan.tenant_id == tenant_id,
            ClientServicePlan.service_catalogue_id == service_catalogue_id,
        )
    ).all()
    return {key: status for key, status in rows if key}


def list_scope_targets(
    db: Session,
    *,
    tenant_id: int,
    clients: list[Client],
    catalogue: ServiceCatalogue,
) -> list[ScopeTarget]:
    client_map = {int(c.id): c for c in clients}
    client_ids = list(client_map)
    if not client_ids:
        return []

    selected_scope = normalize_scope_type(catalogue.service_scope_type)
    required_registration = normalize_registration_type(catalogue.required_registration_type)
    plan_status = _plan_status_map(db, tenant_id, catalogue.id)
    result: list[ScopeTarget] = []

    if selected_scope == "client":
        for client in clients:
            key = scope_key("client", client.id)
            result.append(ScopeTarget(
                token=f"client:{client.id}", scope_type="client", scope_key=key,
                client_id=client.id, business_unit_id=None, client_branch_id=None, registration_id=None,
                client_code=client.client_code or "", client_name=client.client_name or "",
                pan=client.pan or "", business_unit="", client_branch="", registration_type="",
                registration_number="", trade_name=client.trade_name or "", state=client.state or "",
                entity_type=client.client_type or "", partner_id=client.partner_id,
                existing_plan_status=plan_status.get(key, ""),
            ))
        return result

    businesses = db.execute(
        select(ClientBusinessUnit).where(
            ClientBusinessUnit.tenant_id == tenant_id,
            ClientBusinessUnit.client_id.in_(client_ids),
            ClientBusinessUnit.is_active.is_(True),
        ).order_by(ClientBusinessUnit.client_id, ClientBusinessUnit.is_primary.desc(), ClientBusinessUnit.business_name)
    ).scalars().all()

    if selected_scope == "business_unit":
        actual_clients = set()
        for business in businesses:
            client = client_map[business.client_id]
            actual_clients.add(client.id)
            key = scope_key("business_unit", business.id)
            result.append(ScopeTarget(
                token=f"business:{business.id}", scope_type="business_unit", scope_key=key,
                client_id=client.id, business_unit_id=business.id, client_branch_id=None, registration_id=None,
                client_code=client.client_code or "", client_name=client.client_name or "", pan=client.pan or "",
                business_unit=business.business_name, client_branch="", registration_type="", registration_number="",
                trade_name=business.trade_name or client.trade_name or "", state=client.state or "",
                entity_type=client.client_type or "", partner_id=client.partner_id,
                existing_plan_status=plan_status.get(key, ""),
            ))
        for client in clients:
            if client.id not in actual_clients:
                result.append(ScopeTarget(
                    token=f"business:auto:{client.id}", scope_type="business_unit", scope_key=f"AUTO_BUSINESS:{client.id}",
                    client_id=client.id, business_unit_id=None, client_branch_id=None, registration_id=None,
                    client_code=client.client_code or "", client_name=client.client_name or "", pan=client.pan or "",
                    business_unit=client.trade_name or client.client_name, client_branch="", registration_type="",
                    registration_number="", trade_name=client.trade_name or "", state=client.state or "",
                    entity_type=client.client_type or "", partner_id=client.partner_id, existing_plan_status="",
                ))
        return result

    business_map = {b.id: b for b in businesses}
    branches = db.execute(
        select(ClientBranch).where(
            ClientBranch.tenant_id == tenant_id,
            ClientBranch.client_id.in_(client_ids),
            ClientBranch.is_active.is_(True),
        ).order_by(ClientBranch.client_id, ClientBranch.is_primary.desc(), ClientBranch.branch_name)
    ).scalars().all()

    if selected_scope == "client_branch":
        for branch in branches:
            client = client_map[branch.client_id]
            business = business_map.get(branch.business_unit_id)
            key = scope_key("client_branch", branch.id)
            result.append(ScopeTarget(
                token=f"branch:{branch.id}", scope_type="client_branch", scope_key=key,
                client_id=client.id, business_unit_id=branch.business_unit_id, client_branch_id=branch.id, registration_id=None,
                client_code=client.client_code or "", client_name=client.client_name or "", pan=client.pan or "",
                business_unit=business.business_name if business else "", client_branch=branch.branch_name,
                registration_type="", registration_number="", trade_name=(business.trade_name if business else "") or client.trade_name or "",
                state=branch.state or client.state or "", entity_type=client.client_type or "", partner_id=client.partner_id,
                existing_plan_status=plan_status.get(key, ""),
            ))
        return result

    branch_map = {b.id: b for b in branches}
    registration_rows = db.execute(
        select(ClientRegistration, RegistrationType).join(
            RegistrationType, RegistrationType.id == ClientRegistration.registration_type_id
        ).where(
            ClientRegistration.tenant_id == tenant_id,
            ClientRegistration.client_id.in_(client_ids),
            ClientRegistration.status.in_(("active", "valid", "registered")),
            RegistrationType.is_active.is_(True),
        ).order_by(ClientRegistration.client_id, RegistrationType.code, ClientRegistration.registration_number)
    ).all()

    actual_legacy = set()
    for registration, registration_type in registration_rows:
        registration_code = (registration_type.code or "").strip().upper()
        if required_registration and registration_code != required_registration:
            continue
        client = client_map[registration.client_id]
        business = business_map.get(registration.business_unit_id)
        branch = branch_map.get(registration.client_branch_id)
        key = scope_key("registration", registration.id)
        actual_legacy.add((client.id, registration_code))
        result.append(ScopeTarget(
            token=f"registration:{registration.id}", scope_type="registration", scope_key=key,
            client_id=client.id, business_unit_id=registration.business_unit_id,
            client_branch_id=registration.client_branch_id, registration_id=registration.id,
            client_code=client.client_code or "", client_name=client.client_name or "", pan=client.pan or "",
            business_unit=business.business_name if business else "", client_branch=branch.branch_name if branch else "",
            registration_type=registration_code, registration_number=registration.registration_number,
            trade_name=registration.trade_name or (business.trade_name if business else "") or client.trade_name or "",
            state=registration.state or (branch.state if branch else "") or client.state or "",
            entity_type=client.client_type or "", partner_id=client.partner_id,
            existing_plan_status=plan_status.get(key, ""),
        ))

    # Existing legacy GSTIN/TAN values remain usable immediately after migration.
    for client in clients:
        legacy_pairs = []
        if required_registration in (None, "GST") and client.gstin:
            legacy_pairs.append(("GST", client.gstin))
        if required_registration in (None, "TAN") and client.tan:
            legacy_pairs.append(("TAN", client.tan))
        for reg_type, number in legacy_pairs:
            if (client.id, reg_type) in actual_legacy:
                continue
            result.append(ScopeTarget(
                token=f"registration:legacy:{reg_type}:{client.id}", scope_type="registration",
                scope_key=f"LEGACY_{reg_type}:{client.id}", client_id=client.id,
                business_unit_id=None, client_branch_id=None, registration_id=None,
                client_code=client.client_code or "", client_name=client.client_name or "", pan=client.pan or "",
                business_unit=client.trade_name or client.client_name, client_branch="Primary Branch",
                registration_type=reg_type, registration_number=number, trade_name=client.trade_name or "",
                state=client.state or "", entity_type=client.client_type or "", partner_id=client.partner_id,
                existing_plan_status="",
            ))
    return result


def _next_code(prefix: str, value: int) -> str:
    return f"{prefix}{int(value):05d}"


def resolve_scope_target(
    db: Session,
    *,
    tenant_id: int,
    clients_by_id: dict[int, Client],
    token: str,
    actor_user_id: int,
):
    parts = (token or "").split(":")
    if not parts:
        raise ValueError("Invalid subscription scope.")

    kind = parts[0]
    if kind == "client" and len(parts) == 2:
        client = clients_by_id.get(int(parts[1]))
        if not client:
            raise ValueError("Client is outside the permitted scope.")
        return client, "client", scope_key("client", client.id), None, None, None

    if kind == "business":
        if len(parts) == 2:
            business = db.get(ClientBusinessUnit, int(parts[1]))
            if not business or business.tenant_id != tenant_id or business.client_id not in clients_by_id:
                raise ValueError("Business Unit is outside the permitted scope.")
        elif len(parts) == 3 and parts[1] == "auto":
            client = clients_by_id.get(int(parts[2]))
            if not client:
                raise ValueError("Client is outside the permitted scope.")
            business = ClientBusinessUnit(
                tenant_id=tenant_id, client_id=client.id,
                business_code=_next_code("BU", client.id),
                business_name=client.trade_name or client.client_name,
                trade_name=client.trade_name, is_primary=True, is_active=True,
                created_by_user_id=actor_user_id, updated_by_user_id=actor_user_id,
            )
            db.add(business); db.flush()
        else:
            raise ValueError("Invalid Business Unit.")
        client = clients_by_id[business.client_id]
        return client, "business_unit", scope_key("business_unit", business.id), business.id, None, None

    if kind == "branch" and len(parts) == 2:
        branch = db.get(ClientBranch, int(parts[1]))
        if not branch or branch.tenant_id != tenant_id or branch.client_id not in clients_by_id:
            raise ValueError("Client Branch is outside the permitted scope.")
        client = clients_by_id[branch.client_id]
        return client, "client_branch", scope_key("client_branch", branch.id), branch.business_unit_id, branch.id, None

    if kind == "registration":
        if len(parts) == 2:
            reg = db.get(ClientRegistration, int(parts[1]))
            if not reg or reg.tenant_id != tenant_id or reg.client_id not in clients_by_id:
                raise ValueError("Registration is outside the permitted scope.")
        elif len(parts) == 4 and parts[1] == "legacy":
            reg_type = parts[2].upper()
            client = clients_by_id.get(int(parts[3]))
            if not client:
                raise ValueError("Client is outside the permitted scope.")
            number = client.gstin if reg_type == "GST" else client.tan if reg_type == "TAN" else None
            if not number:
                raise ValueError("Legacy registration is no longer available.")
            business = db.execute(
                select(ClientBusinessUnit).where(
                    ClientBusinessUnit.tenant_id == tenant_id,
                    ClientBusinessUnit.client_id == client.id,
                    ClientBusinessUnit.is_active.is_(True),
                ).order_by(ClientBusinessUnit.is_primary.desc(), ClientBusinessUnit.id)
            ).scalars().first()
            if not business:
                business = ClientBusinessUnit(
                    tenant_id=tenant_id, client_id=client.id,
                    business_code=_next_code("BU", client.id),
                    business_name=client.trade_name or client.client_name,
                    trade_name=client.trade_name, is_primary=True, is_active=True,
                    created_by_user_id=actor_user_id, updated_by_user_id=actor_user_id,
                )
                db.add(business); db.flush()
            branch = db.execute(
                select(ClientBranch).where(
                    ClientBranch.tenant_id == tenant_id,
                    ClientBranch.business_unit_id == business.id,
                    ClientBranch.is_active.is_(True),
                ).order_by(ClientBranch.is_primary.desc(), ClientBranch.id)
            ).scalars().first()
            if not branch:
                branch = ClientBranch(
                    tenant_id=tenant_id, client_id=client.id, business_unit_id=business.id,
                    branch_code=_next_code("BR", client.id), branch_name="Primary Branch",
                    branch_type="head_office", state=client.state, is_primary=True, is_active=True,
                    created_by_user_id=actor_user_id, updated_by_user_id=actor_user_id,
                )
                db.add(branch); db.flush()
            registration_type = db.execute(
                select(RegistrationType).where(RegistrationType.code == reg_type)
            ).scalar_one_or_none()
            if not registration_type:
                registration_type = RegistrationType(
                    code=reg_type,
                    name=reg_type,
                    category="registration",
                    identifier_label=f"{reg_type} Number",
                    supports_expiry=False,
                    supports_related_person=False,
                    is_system=True,
                    is_active=True,
                    sort_order=0,
                )
                db.add(registration_type)
                db.flush()
            reg = ClientRegistration(
                tenant_id=tenant_id,
                branch_id=getattr(client, "branch_id", None),
                client_id=client.id,
                business_unit_id=business.id,
                client_branch_id=branch.id,
                registration_type_id=registration_type.id,
                registration_number=number.strip().upper(),
                legal_name=client.client_name,
                trade_name=client.trade_name,
                state=client.state,
                jurisdiction=client.state,
                status="active",
                primary_registration=True,
                responsible_party="firm",
                auto_create_task=True,
                created_by_user_id=actor_user_id,
            )
            db.add(reg); db.flush()
        else:
            raise ValueError("Invalid registration.")
        client = clients_by_id[reg.client_id]
        return client, "registration", scope_key("registration", reg.id), reg.business_unit_id, reg.client_branch_id, reg.id

    raise ValueError("Invalid subscription scope.")
