from __future__ import annotations

from sqlalchemy import exists, func, or_, select
from sqlalchemy.orm import Session

from app.modules.client_groups.models import ClientGroup
from app.modules.clients.models import Client
from app.modules.services.models import ClientServiceSubscription

GROUP_TYPES = ("Family", "Business Group", "Promoter Group", "Trust Group", "Common Management", "Other")


def normalise_group_code(value: str | None) -> str:
    return (value or "").strip().upper()


def list_groups(db: Session, *, tenant_id: int, include_inactive: bool = False, viewer_partner_id: int | None = None):
    stmt = (
        select(ClientGroup, func.count(Client.id).label("client_count"))
        .outerjoin(Client, Client.client_group_id == ClientGroup.id)
        .where(ClientGroup.tenant_id == tenant_id)
        .group_by(ClientGroup.id)
        .order_by(ClientGroup.group_name.asc())
    )
    if viewer_partner_id is not None:
        review_access = exists(select(ClientServiceSubscription.id).where(
            ClientServiceSubscription.tenant_id == tenant_id,
            ClientServiceSubscription.client_id == Client.id,
            ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
            ClientServiceSubscription.is_active.is_(True),
        ))
        stmt = stmt.where(or_(Client.partner_id == viewer_partner_id, review_access))
    if not include_inactive:
        stmt = stmt.where(ClientGroup.is_active.is_(True))
    return [{"group": group, "client_count": int(count or 0)} for group, count in db.execute(stmt).all()]


def get_group(db: Session, *, tenant_id: int, group_id: int):
    return db.execute(select(ClientGroup).where(ClientGroup.id == group_id, ClientGroup.tenant_id == tenant_id)).scalar_one_or_none()


def get_group_by_code(db: Session, *, tenant_id: int, group_code: str):
    code = normalise_group_code(group_code)
    if not code:
        return None
    return db.execute(select(ClientGroup).where(ClientGroup.tenant_id == tenant_id, ClientGroup.group_code == code)).scalar_one_or_none()


def list_group_clients(db: Session, *, tenant_id: int, group_id: int, viewer_partner_id: int | None = None):
    stmt = select(Client).where(Client.tenant_id == tenant_id, Client.client_group_id == group_id, Client.is_archived.is_(False))
    if viewer_partner_id is not None:
        review_access = exists(select(ClientServiceSubscription.id).where(
            ClientServiceSubscription.tenant_id == tenant_id,
            ClientServiceSubscription.client_id == Client.id,
            ClientServiceSubscription.review_partner_user_id == viewer_partner_id,
            ClientServiceSubscription.is_active.is_(True),
        ))
        stmt = stmt.where(or_(Client.partner_id == viewer_partner_id, review_access))
    return db.execute(stmt.order_by(Client.is_group_head.desc(), Client.client_name.asc())).scalars().all()


def create_group(db: Session, *, tenant_id: int, actor_user_id: int, payload: dict):
    code = normalise_group_code(payload.get("group_code"))
    name = (payload.get("group_name") or "").strip()
    if not code or not name:
        raise ValueError("Group code and group name are required.")
    if get_group_by_code(db, tenant_id=tenant_id, group_code=code):
        raise ValueError("Client group code already exists in this firm.")
    group_type = (payload.get("group_type") or "Family").strip()
    if group_type not in GROUP_TYPES:
        raise ValueError("Invalid group type.")
    row = ClientGroup(
        tenant_id=tenant_id, group_code=code, group_name=name, group_type=group_type,
        primary_contact_name=(payload.get("primary_contact_name") or "").strip() or None,
        primary_contact_mobile=(payload.get("primary_contact_mobile") or "").strip() or None,
        primary_contact_email=(payload.get("primary_contact_email") or "").strip().lower() or None,
        assigned_partner_user_id=payload.get("assigned_partner_user_id") or None,
        primary_consultant_id=payload.get("primary_consultant_id") or None,
        notes=(payload.get("notes") or "").strip() or None,
        is_active=bool(payload.get("is_active", True)), created_by_user_id=actor_user_id, updated_by_user_id=actor_user_id,
    )
    db.add(row); db.commit(); db.refresh(row); return row


def update_group(db: Session, *, row: ClientGroup, actor_user_id: int, payload: dict):
    code = normalise_group_code(payload.get("group_code"))
    name = (payload.get("group_name") or "").strip()
    if not code or not name:
        raise ValueError("Group code and group name are required.")
    duplicate = get_group_by_code(db, tenant_id=row.tenant_id, group_code=code)
    if duplicate and duplicate.id != row.id:
        raise ValueError("Client group code already exists in this firm.")
    group_type = (payload.get("group_type") or "Family").strip()
    if group_type not in GROUP_TYPES:
        raise ValueError("Invalid group type.")
    for key, value in {
        "group_code": code, "group_name": name, "group_type": group_type,
        "primary_contact_name": (payload.get("primary_contact_name") or "").strip() or None,
        "primary_contact_mobile": (payload.get("primary_contact_mobile") or "").strip() or None,
        "primary_contact_email": (payload.get("primary_contact_email") or "").strip().lower() or None,
        "assigned_partner_user_id": payload.get("assigned_partner_user_id") or None,
        "primary_consultant_id": payload.get("primary_consultant_id") or None,
        "notes": (payload.get("notes") or "").strip() or None,
        "is_active": bool(payload.get("is_active", False)), "updated_by_user_id": actor_user_id,
    }.items(): setattr(row, key, value)
    db.add(row); db.commit(); db.refresh(row); return row


def resolve_or_create_group(db: Session, *, tenant_id: int, actor_user_id: int, group_code: str | None, group_name: str | None, group_type: str | None = None):
    code = normalise_group_code(group_code)
    name = (group_name or "").strip()
    if not code and not name:
        return None
    if not code:
        raise ValueError("client_group_code is required when client_group_name is supplied.")
    existing = get_group_by_code(db, tenant_id=tenant_id, group_code=code)
    if existing:
        if name and existing.group_name.strip().lower() != name.lower():
            raise ValueError(f"Client group code {code} already exists with name {existing.group_name}.")
        return existing
    if not name:
        raise ValueError(f"Client group {code} does not exist; provide client_group_name to create it.")
    return create_group(db, tenant_id=tenant_id, actor_user_id=actor_user_id, payload={"group_code": code, "group_name": name, "group_type": group_type or "Family", "is_active": True})
