import logging
import time
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Response

from sqlalchemy.ext.asyncio import AsyncSession

from src.services.org_unit_service import OrgUnitService
from src.services.budget_line_service import BudgetLineService
from src.db.models.form_type import FormTypeEnum
from src.services.sheet_service import SheetService
from src.domain.schemas import AddLineSchema, BaseListResponse, BaseSingleResponse, BudgetFormResponse, CellPatch, CellsPatch, DirectionSchemaEnum, FormCreateSchema, SheetFormTypeResponse, SheetResponse
from src.db.models.app_user import AppUser
from src.services.budget_form_service import BudgetFormService
from src.api.v1.deps import get_current_active_user_with_set_db, require_admin
from src.db.session import get_db

router = APIRouter(prefix="/form", tags=["forms"])
SHEETS_WITH_SECTIONS = {"AHR", "CAP", "OPER"}
FORM1_DIRECTION_REQUIRED_SHEETS = {"AHR", "CAP"}


logger = logging.getLogger()


def _rows_to_sheet_response(result: list[tuple]) -> list[SheetResponse]:
    return [
        SheetResponse(
            row_type=el[0],
            depth=el[1],
            sort_order=el[2],
            data=el[3],
        ) for el in result
    ]


def _parse_sections_csv(sections: Optional[str]) -> Optional[list[str]]:
    if sections is None:
        return None
    parsed = [section.strip() for section in sections.split(",") if section.strip()]
    return parsed or None


def _validate_sheet_query_params(
    form_type_code: FormTypeEnum,
    sheet: str,
    direction: Optional[DirectionSchemaEnum],
    sections: Optional[list[str]],
) -> None:
    if form_type_code == FormTypeEnum.FORM_1 and sheet in FORM1_DIRECTION_REQUIRED_SHEETS and not direction:
        raise HTTPException(400, "direction обязателен для FORM_1 листов AHR/CAP")

    if direction and not (
        form_type_code == FormTypeEnum.FORM_1 and sheet in FORM1_DIRECTION_REQUIRED_SHEETS
    ):
        raise HTTPException(400, "direction допустим только для FORM_1 листов AHR/CAP")

    if sections and sheet not in SHEETS_WITH_SECTIONS:
        raise HTTPException(400, f"sections не поддерживается для листа {sheet}")


@router.get("/")
async def get_forms(
    offset: int = 0,
    limit: int = 100,
    db: AsyncSession = Depends(get_db),
    current_user: AppUser = Depends(get_current_active_user_with_set_db),
) -> BaseListResponse[BudgetFormResponse]:
    """Все строки v3.budget_form для UI-выпадашки."""
    bf_service = BudgetFormService(db)
    count, forms = await bf_service.get_list(
        user=current_user, 
        with_count=True,
        offset=offset,
        limit=limit,
        load_org=True,
    )
    return BaseListResponse(
        result = [
            BudgetFormResponse.model_validate(form) for form in forms
        ],
        count=count,
    )


@router.get("/{form_id}")
async def get_form(
    form_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: AppUser = Depends(get_current_active_user_with_set_db),
) -> BaseSingleResponse[BudgetFormResponse]:
    bf_service = BudgetFormService(db)
    form = await bf_service.get(
        user=current_user, 
        budget_form_id=form_id,
        load_org=True,
    )
    return BaseSingleResponse(
        result = BudgetFormResponse.model_validate(form),
    )


@router.get("/{form_id}/sheets")
async def get_form_sheets(
    form_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: AppUser = Depends(get_current_active_user_with_set_db),
) -> BaseSingleResponse[SheetFormTypeResponse]:
    bf_service = BudgetFormService(db)
    form = await bf_service.get(budget_form_id=form_id, user=current_user, load_form_type=True)

    if not form:
        raise HTTPException(404, f"Форма {form_id} не найдена")
    return BaseSingleResponse(
        result=SheetFormTypeResponse(
            form_type=form.form_type.code,
            sheets=form.form_type.sheet_list,
            all_sheets=form.form_type.sheet_list_with_directions,
        )
    )


@router.get("/{form_id}/sheet/{sheet}")
async def get_sheet(
        form_id: int, 
        sheet: str, 
        response: Response,
        direction: Optional[DirectionSchemaEnum] = None,
        sections: Optional[str] = None,
        db: AsyncSession = Depends(get_db),
        current_user: AppUser = Depends(get_current_active_user_with_set_db),
) -> BaseListResponse[SheetResponse]:
    sections = _parse_sections_csv(sections)
    t0 = time.perf_counter()
    bf_service = BudgetFormService(db)
    sheet_service = SheetService(db)

    form = await bf_service.get(
        user=current_user,
        budget_form_id=form_id,
        load_form_type=True,
    )
    if not form:
        raise HTTPException(404, f"Форма {form_id} не найдена")
    if sheet not in form.form_type.sheet_list:
        raise HTTPException(404, f"Лист {sheet} формы {form_id} не найден")
    _validate_sheet_query_params(form.form_type.code, sheet, direction, sections)
    if sections and (rem_sects := set(sections) - set(form.form_type.section_list)):
        raise HTTPException(400, f"Недопустимые sections: {', '.join(sorted(rem_sects))}")

    result = await sheet_service.get(
        form_id=form_id,
        sheet=sheet,
        direction=direction.value if direction else None,
        sections=sections,
    )
    db_ms = (time.perf_counter() - t0) * 1000
    response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
    extra={
        "type": "db_time",
        "time_ms": f"{db_ms:.2f}", 
        "handler": "get_sheet",
        "form_id": form_id,
        "sheet": sheet,
        "direction": direction,
        "sections": sections,
        "user": current_user.id,
    }
    logger.info(
        f"db_time: {extra}", 
        extra=extra,
    )
    return BaseListResponse(
            count=len(result),
            result=_rows_to_sheet_response(result),
        )


@router.patch("/{form_id}/sheet/{sheet}/cell")
async def update_cell(
        form_id: int, 
        sheet: str, 
        response: Response,
        cell_body: CellPatch,
        direction: Optional[DirectionSchemaEnum] = None,
        sections: Optional[str] = None,
        db: AsyncSession = Depends(get_db),
        current_user: AppUser = Depends(get_current_active_user_with_set_db),
) -> BaseListResponse[SheetResponse]:
    sections = _parse_sections_csv(sections)
    t0 = time.perf_counter()
    bf_service = BudgetFormService(db)
    sheet_service = SheetService(db)

    form = await bf_service.get(
        user=current_user,
        budget_form_id=form_id,
        load_form_type=True,
    )
    if not form:
        raise HTTPException(404, f"Форма {form_id} не найдена")
    if sheet not in form.form_type.sheet_list:
        raise HTTPException(404, f"Лист {sheet} формы {form_id} не найден")
    _validate_sheet_query_params(form.form_type.code, sheet, direction, sections)
    if sections and (rem_sects := set(sections) - set(form.form_type.section_list)):
        raise HTTPException(400, f"Недопустимые sections: {', '.join(sorted(rem_sects))}")

    result = await sheet_service.update_cell(
        form_id=form_id, 
        sheet=sheet,
        direction=direction.value if direction else None,
        sections=sections,
        line_id=cell_body.line_id, 
        column=cell_body.column, 
        value=cell_body.value, 
        user=current_user,
    )
    db_ms = (time.perf_counter() - t0) * 1000
    response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
    extra={
        "type": "db_time",
        "time_ms": f"{db_ms:.2f}", 
        "handler": "update_form_cell",
        "form_id": form_id,
        "sheet": sheet,
        "direction": direction,
        "sections": sections,
        "user": current_user.id,
        "line_id": cell_body.line_id, 
        "column": cell_body.column, 
    }
    logger.info(
        f"db_time: {extra}", 
        extra=extra,
    )
    return BaseListResponse(
            count=len(result),
            result=_rows_to_sheet_response(result),
        )




@router.patch("/{form_id}/sheet/{sheet}/cells")
async def update_cells(
        form_id: int, 
        sheet: str, 
        response: Response,
        cells_body: CellsPatch,
        direction: Optional[DirectionSchemaEnum] = None,
        sections: Optional[str] = None,
        db: AsyncSession = Depends(get_db),
        current_user: AppUser = Depends(get_current_active_user_with_set_db),
) -> BaseListResponse[SheetResponse]:
    sections = _parse_sections_csv(sections)
    t0 = time.perf_counter()
    bf_service = BudgetFormService(db)
    sheet_service = SheetService(db)

    form = await bf_service.get(
        user=current_user,
        budget_form_id=form_id,
        load_form_type=True,
    )
    if not form:
        raise HTTPException(404, f"Форма {form_id} не найдена")
    if sheet not in form.form_type.sheet_list:
        raise HTTPException(404, f"Лист {sheet} формы {form_id} не найден")
    _validate_sheet_query_params(form.form_type.code, sheet, direction, sections)
    if sections and (rem_sects := set(sections) - set(form.form_type.section_list)):
        raise HTTPException(400, f"Недопустимые sections: {', '.join(sorted(rem_sects))}")

    result = await sheet_service.update_cells(
        form_id=form_id, 
        sheet=sheet,
        direction=direction.value if direction else None,
        sections=sections,
        changes=[c.model_dump() for c in cells_body.changes],
        user=current_user,
    )
    db_ms = (time.perf_counter() - t0) * 1000
    response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
    extra={
        "type": "db_time",
        "time_ms": f"{db_ms:.2f}", 
        "handler": "update_form_cells",
        "form_id": form_id,
        "sheet": sheet,
        "direction": direction,
        "sections": sections,
        "user": current_user.id,
        "count": len(cells_body.changes)
    }
    logger.info(
        f"db_time: {extra}", 
        extra=extra,
    )
    return BaseListResponse(
            count=len(result),
            result=_rows_to_sheet_response(result),
    )



@router.post("/{form_id}/sheet/{sheet}/line")
async def add_line(
        form_id: int, 
        sheet: str, 
        body: AddLineSchema,
        response: Response,
        db: AsyncSession = Depends(get_db),
        current_user: AppUser = Depends(get_current_active_user_with_set_db),
):
    t0 = time.perf_counter()
    bf_service = BudgetFormService(db)
    sheet_service = SheetService(db)

    form = await bf_service.get(
        user=current_user,
        budget_form_id=form_id,
        load_form_type=True,
    )
    if not form:
        raise HTTPException(404, f"Форма {form_id} не найдена")
    if sheet not in form.form_type.sheet_list:
        raise HTTPException(404, f"Лист {sheet} формы {form_id} не найден")
    _validate_sheet_query_params(form.form_type.code, sheet, body.direction, None)

    result = await sheet_service.add_line(
        form_id=form_id,
        sheet=sheet,
        expense_item_id=body.expense_item_id,
        item_id=body.item_id,
        section_code=body.section_code,
        direction=body.direction.value if body.direction else None,
        name=body.name,
        internal_order=body.internal_order,
        vsp_id=body.vsp_id,
        project_id=body.project_id,
        justification=body.justification,
        contract_number=body.contract_number,
        contract_end_date=body.contract_end_date,
        user=current_user,
    )
    db_ms = (time.perf_counter() - t0) * 1000
    response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"

    extra={
        "type": "db_time",
        "time_ms": f"{db_ms:.2f}", 
        "handler": "add_form_line",
        "form_id": form_id,
        "sheet": sheet,
        "direction": body.direction,
        "user": current_user.id,
        "expense_item_id": body.expense_item_id
    }
    logger.info(
        f"db_time: {extra}", 
        extra=extra,
    )
    return BaseListResponse(
            count=len(result),
            result=_rows_to_sheet_response(result),
    )



@router.delete("/{form_id}/sheet/{sheet}/line/{row_id}")
async def delete_line(
        form_id: int, 
        sheet: str, 
        row_id: int,
        response: Response,
        direction: Optional[DirectionSchemaEnum] = None,
        db: AsyncSession = Depends(get_db),
        current_user: AppUser = Depends(get_current_active_user_with_set_db),
):
    t0 = time.perf_counter()
    sheet_service = SheetService(db)
    bf_service = BudgetFormService(db)

    form = await bf_service.get(
        user=current_user,
        budget_form_id=form_id,
        load_form_type=True,
    )
    if not form:
        raise HTTPException(404, f"Форма {form_id} не найдена")
    if sheet not in form.form_type.sheet_list:
        raise HTTPException(404, f"Лист {sheet} формы {form_id} не найден")
    _validate_sheet_query_params(form.form_type.code, sheet, direction, None)
    
    result = await sheet_service.delete_line(
        form_id=form_id,
        sheet=sheet,
        row_id=row_id,
        direction=direction.value if direction else None,
        user=current_user,
    )
    db_ms = (time.perf_counter() - t0) * 1000
    response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
    extra={
        "type": "db_time",
        "time_ms": f"{db_ms:.2f}", 
        "handler": "del_form_line",
        "form_id": form_id,
        "sheet": sheet,
        "direction": direction,
        "user": current_user.id,
        "line_id": row_id,
    }
    logger.info(
        f"db_time: {extra}", 
        extra=extra,
    )

    return BaseListResponse(
        count=len(result),
        result=_rows_to_sheet_response(result),
    )


@router.post("/")
async def add_form(
        response: Response,
        form: FormCreateSchema,
        db: AsyncSession = Depends(get_db),
        current_user: AppUser = Depends(require_admin),
) -> BaseListResponse[BudgetFormResponse]:
    t0 = time.perf_counter()
    bf_service = BudgetFormService(db)
    org_service = OrgUnitService(db)

    org_units = await org_service.get_list(user=current_user, org_unit_id=form.org_unit_ids)
    org_unit_ids = [o.id for o in org_units]
    if len(org_units) != len(form.org_unit_ids):
        not_found_ids = ", ".join(
            [str(oid) for oid in set(form.org_unit_ids) - set(org_unit_ids)],
        )
        raise HTTPException(404, f"ССП {not_found_ids} не найдены")

    new_forms = await bf_service.bulk_create_if_not_exists(
        form_type_code=form.form_type_code,
        year=form.year,
        org_unit_ids=org_unit_ids,
    )

    db_ms = (time.perf_counter() - t0) * 1000
    response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
    extra={
        "type": "db_time",
        "time_ms": f"{db_ms:.2f}", 
        "handler": "add_form",
        "form_type_code": form.form_type_code,
        "user": current_user.id,
        "count_orgs": len(form.org_unit_ids),
    }
    logger.info(
        f"db_time: {extra}", 
        extra=extra,
    )

    return BaseListResponse(
        result=[BudgetFormResponse.model_validate(new_form) for new_form in new_forms],
        count=len(new_forms),
    )
