from __future__ import annotations

from datetime import date, datetime
from decimal import Decimal

from fastapi import HTTPException

from .db import get_conn


DB_STATUS_TABLES = [
    {
        "table_ref": "public.members",
        "label": "구성원 마스터",
        "domain": "organization",
        "timestamp_column": "updated_at",
        "related_views": ["조직 현황", "자리배치도"],
        "description": "조직/구성원 화면의 기준이 되는 현재 인원 마스터",
    },
    {
        "table_ref": "public.member_versions",
        "label": "구성원 이력",
        "domain": "history",
        "timestamp_column": "created_at",
        "related_views": ["조직 현황", "이력 비교"],
        "description": "as-of 조회와 변경 이력을 위한 시점 버전",
    },
    {
        "table_ref": "public.seat_maps",
        "label": "자리배치도 도면",
        "domain": "seatmap",
        "timestamp_column": "updated_at",
        "related_views": ["자리배치도"],
        "description": "오피스별 도면 메타데이터와 활성 상태",
    },
    {
        "table_ref": "public.seat_positions",
        "label": "현재 좌석 배치",
        "domain": "seatmap",
        "timestamp_column": "updated_at",
        "related_views": ["자리배치도"],
        "description": "현재 인원의 실제 배치 좌표/슬롯 연결",
    },
    {
        "table_ref": "public.seat_assignment_versions",
        "label": "좌석 배치 이력",
        "domain": "history",
        "timestamp_column": "created_at",
        "related_views": ["자리배치도", "이력 비교"],
        "description": "자리 이동 이력과 시점 조회용 배치 버전",
    },
    {
        "table_ref": "public.integration_import_batches",
        "label": "원본 업로드 배치",
        "domain": "integration",
        "timestamp_column": "imported_at",
        "related_views": ["프로젝트별 분석", "팀/개인별 분석", "조직 현황"],
        "description": "원본 파일 적재 단위와 최근 import 기록",
    },
    {
        "table_ref": "public.integration_projects",
        "label": "통합 프로젝트 표준화",
        "domain": "integration",
        "timestamp_column": "updated_at",
        "related_views": ["프로젝트별 분석", "팀/개인별 분석"],
        "description": "프로젝트 코드/이름/카테고리 정규화 결과",
    },
    {
        "table_ref": "public.integration_work_logs",
        "label": "근무 로그 표준화",
        "domain": "integration",
        "timestamp_column": "updated_at",
        "related_views": ["팀/개인별 분석"],
        "description": "MH workbook 기준 일자별 근무 로그 본체",
    },
    {
        "table_ref": "public.integration_work_log_segments",
        "label": "근무 로그 세그먼트",
        "domain": "integration",
        "timestamp_column": "created_at",
        "related_views": ["팀/개인별 분석"],
        "description": "근무 로그를 프로젝트/활동 기준으로 분해한 상세 세그먼트",
    },
    {
        "table_ref": "public.integration_vouchers",
        "label": "전표 표준화",
        "domain": "integration",
        "timestamp_column": "created_at",
        "related_views": ["프로젝트별 분석"],
        "description": "payment CSV 기준 프로젝트별 수입/지출 전표",
    },
    {
        "table_ref": "public.integration_binary_sources",
        "label": "바이너리 원본 보관",
        "domain": "integration",
        "timestamp_column": "imported_at",
        "related_views": ["사업관리대장"],
        "description": "엑셀/바이너리 원본을 DB에 보관하는 저장소",
    },
    {
        "table_ref": "auth.users",
        "label": "인증 사용자",
        "domain": "auth",
        "timestamp_column": "updated_at",
        "related_views": ["로그인", "권한"],
        "description": "로그인 계정, role, 활성 상태",
    },
    {
        "table_ref": "auth.sessions",
        "label": "인증 세션",
        "domain": "auth",
        "timestamp_column": "created_at",
        "related_views": ["로그인", "권한"],
        "description": "현재/과거 로그인 세션과 만료 상태",
    },
    {
        "table_ref": "auth.login_audit_logs",
        "label": "로그인 감사 로그",
        "domain": "auth",
        "timestamp_column": "created_at",
        "related_views": ["로그인", "권한"],
        "description": "로그인 성공/실패 기록",
    },
]

DB_STATUS_TABLE_META = {str(item["table_ref"]): item for item in DB_STATUS_TABLES}
DB_STATUS_TABLE_GROUPS = {
    "public.members": "유지",
    "public.member_versions": "유지",
    "public.seat_maps": "유지",
    "public.seat_positions": "유지",
    "public.seat_slots": "유지",
    "public.seat_assignment_versions": "유지",
    "public.history_revisions": "유지",
    "public.integration_import_batches": "유지",
    "public.integration_projects": "유지",
    "public.integration_work_logs": "유지",
    "public.integration_work_log_segments": "유지",
    "public.integration_vouchers": "유지",
    "public.integration_binary_sources": "유지",
    "auth.users": "유지",
    "auth.sessions": "유지",
    "auth.login_audit_logs": "유지",
    "public.member_overrides": "주의",
    "public.member_retirements": "주의",
    "public.member_aliases": "주의",
    "public.integration_project_aliases": "주의",
    "public.integration_project_category_mappings": "주의",
    "public.integration_project_pm_assignments": "주의",
    "public.integration_raw_organization_rows": "원본·추적",
    "public.integration_raw_mh_rows": "원본·추적",
    "public.integration_raw_mh_pm_rows": "원본·추적",
    "public.integration_raw_payment_rows": "원본·추적",
}

DB_STATUS_PRODUCT_GROUPS = {
    "탭 데이터": [
        "public.members",
        "public.seat_maps",
        "public.seat_slots",
        "public.seat_positions",
        "public.integration_projects",
        "public.integration_work_logs",
        "public.integration_work_log_segments",
        "public.integration_vouchers",
        "public.integration_binary_sources",
    ],
    "로그인·권한": [
        "auth.users",
        "auth.sessions",
        "auth.login_audit_logs",
    ],
    "히스토리": [
        "public.history_revisions",
        "public.member_versions",
        "public.seat_assignment_versions",
    ],
    "로우데이터·적재": [
        "public.integration_import_batches",
        "public.integration_raw_organization_rows",
        "public.integration_raw_mh_rows",
        "public.integration_raw_mh_pm_rows",
        "public.integration_raw_payment_rows",
    ],
    "보정·보조": [
        "public.member_overrides",
        "public.member_retirements",
        "public.member_aliases",
        "public.integration_project_aliases",
        "public.integration_project_category_mappings",
        "public.integration_project_pm_assignments",
    ],
}

DB_STATUS_SCREEN_MAP = [
    {
        "screen": "조직 현황",
        "tables": [
            "public.members",
            "public.member_overrides",
            "public.member_retirements",
            "public.member_aliases",
            "public.member_versions",
            "public.history_revisions",
        ],
        "write_flow": "원본 조직 데이터 import 후 members 계열을 갱신하고, 수정/이력 기능은 revision 기반으로 누적합니다.",
    },
    {
        "screen": "자리배치도",
        "tables": [
            "public.seat_maps",
            "public.seat_slots",
            "public.seat_positions",
            "public.seat_assignment_versions",
            "public.history_revisions",
            "public.members",
        ],
        "write_flow": "고정 오피스 도면과 현재 좌석 배치를 읽고, 저장 시 현재 배치와 배치 이력을 함께 기록합니다.",
    },
    {
        "screen": "프로젝트별 분석",
        "tables": [
            "public.integration_import_batches",
            "public.integration_projects",
            "public.integration_vouchers",
            "public.integration_project_aliases",
            "public.integration_project_category_mappings",
            "public.integration_project_pm_assignments",
        ],
        "write_flow": "payment 원본 import 결과와 프로젝트 보정 테이블을 조합해 프로젝트 집계를 만듭니다.",
    },
    {
        "screen": "팀/개인별 분석",
        "tables": [
            "public.integration_import_batches",
            "public.integration_projects",
            "public.integration_work_logs",
            "public.integration_work_log_segments",
            "public.integration_raw_mh_rows",
            "public.integration_raw_mh_pm_rows",
        ],
        "write_flow": "MH 원본 row를 적재한 뒤 표준화 로그와 세그먼트로 분해해 화면 집계에 사용합니다.",
    },
    {
        "screen": "사업관리대장",
        "tables": [
            "public.integration_binary_sources",
        ],
        "write_flow": "현재는 기본 바이너리 원본 보관 상태만 DB에 유지하며, 상세 계산 규칙은 별도 기준 정렬이 필요합니다.",
    },
    {
        "screen": "로그인 / 권한",
        "tables": [
            "auth.users",
            "auth.sessions",
            "auth.login_audit_logs",
        ],
        "write_flow": "사용자 계정, 세션, 로그인 감사 로그를 auth 스키마에서 분리 운영합니다.",
    },
]


def make_json_safe(value: object) -> object:
    if isinstance(value, datetime):
        return value.isoformat()
    if isinstance(value, date):
        return value.isoformat()
    if isinstance(value, Decimal):
        return float(value)
    if isinstance(value, bytes):
        return f"<{len(value)} bytes>"
    if isinstance(value, dict):
        return {str(key): make_json_safe(val) for key, val in value.items()}
    if isinstance(value, list):
        return [make_json_safe(item) for item in value]
    return value


def fetch_db_status_snapshot() -> dict[str, object]:
    table_items: list[dict[str, object]] = []
    with get_conn() as conn:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT schemaname, tablename
                FROM pg_tables
                WHERE schemaname IN ('public', 'auth')
                ORDER BY schemaname, tablename
                """
            )
            all_tables = cur.fetchall()
            for row in all_tables:
                schema_name = str(row["schemaname"])
                table_name = str(row["tablename"])
                table_ref = f"{schema_name}.{table_name}"
                spec = DB_STATUS_TABLE_META.get(table_ref, {})
                cur.execute("SELECT to_regclass(%s) IS NOT NULL AS table_exists", (table_ref,))
                exists_row = cur.fetchone()
                exists = bool(exists_row["table_exists"]) if exists_row is not None else False
                row_count = 0
                last_event_at = None
                if exists:
                    timestamp_column = str(spec.get("timestamp_column") or "")
                    query = f"SELECT COUNT(*)::bigint AS row_count"
                    if timestamp_column:
                        query += f", MAX({timestamp_column}) AS last_event_at"
                    else:
                        query += ", NULL::timestamptz AS last_event_at"
                    query += f" FROM {schema_name}.{table_name}"
                    cur.execute(query)
                    metric_row = cur.fetchone() or {}
                    row_count = int(metric_row.get("row_count") or 0)
                    last_event_at = metric_row.get("last_event_at")
                table_items.append(
                    {
                        "table_ref": table_ref,
                        "schema": schema_name,
                        "table_name": table_name,
                        "label": str(spec.get("label") or table_name),
                        "domain": str(spec.get("domain") or "other"),
                        "description": str(spec.get("description") or "세부 보조/원본/운영 테이블"),
                        "related_views": spec.get("related_views") or [],
                        "group": DB_STATUS_TABLE_GROUPS.get(table_ref, "주의"),
                        "exists": exists,
                        "row_count": row_count,
                        "last_event_at": last_event_at.isoformat() if last_event_at else None,
                    }
                )

            cur.execute(
                """
                SELECT source_key, source_name, row_count, source_path, imported_at
                FROM integration_import_batches
                ORDER BY imported_at DESC, id DESC
                """
            )
            import_batches = [
                {
                    "source_key": str(row["source_key"] or ""),
                    "source_name": str(row["source_name"] or ""),
                    "row_count": int(row["row_count"] or 0),
                    "source_path": str(row["source_path"] or ""),
                    "imported_at": row["imported_at"].isoformat() if row.get("imported_at") else None,
                }
                for row in cur.fetchall()
            ]

            binary_sources: list[dict[str, object]] = []
            cur.execute("SELECT to_regclass('public.integration_binary_sources') IS NOT NULL AS table_exists")
            binary_exists_row = cur.fetchone()
            binary_exists = bool(binary_exists_row["table_exists"]) if binary_exists_row is not None else False
            if binary_exists:
                cur.execute(
                    """
                    SELECT source_key, source_name, filename, mime_type, OCTET_LENGTH(content) AS byte_size,
                           content_sha256, imported_at
                    FROM integration_binary_sources
                    ORDER BY imported_at DESC, id DESC
                    """
                )
                binary_sources = [
                    {
                        "source_key": str(row["source_key"] or ""),
                        "source_name": str(row["source_name"] or ""),
                        "filename": str(row["filename"] or ""),
                        "mime_type": str(row["mime_type"] or ""),
                        "byte_size": int(row["byte_size"] or 0),
                        "content_sha256": str(row["content_sha256"] or ""),
                        "imported_at": row["imported_at"].isoformat() if row.get("imported_at") else None,
                    }
                    for row in cur.fetchall()
                ]

            cur.execute(
                """
                SELECT COUNT(*)::bigint AS total_members,
                       COUNT(*) FILTER (
                           WHERE COALESCE(BTRIM(work_status), '') <> '퇴직'
                       )::bigint AS active_members
                FROM members
                """
            )
            member_row = cur.fetchone() or {}

            cur.execute(
                """
                SELECT COUNT(*)::bigint AS active_seat_maps
                FROM seat_maps
                WHERE is_active = TRUE
                """
            )
            seat_map_row = cur.fetchone() or {}

            cur.execute(
                """
                SELECT COUNT(*)::bigint AS fixed_office_maps
                FROM seat_maps
                WHERE source_type = 'fixed_html'
                """
            )
            fixed_office_row = cur.fetchone() or {}

    overview = {
        "visible_tables": len(DB_STATUS_TABLES),
        "total_tables": len(table_items),
        "existing_tables": sum(1 for item in table_items if item["exists"]),
        "registered_members": int(member_row.get("total_members") or 0),
        "active_members": int(member_row.get("active_members") or 0),
        "active_seat_maps": int(seat_map_row.get("active_seat_maps") or 0),
        "fixed_office_maps": int(fixed_office_row.get("fixed_office_maps") or 0),
        "import_batches": len(import_batches),
        "binary_sources": len(binary_sources),
    }
    group_summary = {
        "유지": [item["table_ref"] for item in table_items if item["group"] == "유지"],
        "주의": [item["table_ref"] for item in table_items if item["group"] == "주의"],
        "원본·추적": [item["table_ref"] for item in table_items if item["group"] == "원본·추적"],
        "정리 후보": [item["table_ref"] for item in table_items if item["group"] == "정리 후보"],
    }
    product_summary = {
        group_name: table_refs
        for group_name, table_refs in DB_STATUS_PRODUCT_GROUPS.items()
    }
    return {
        "generated_at": datetime.utcnow().isoformat() + "Z",
        "overview": overview,
        "tables": table_items,
        "import_batches": import_batches,
        "binary_sources": binary_sources,
        "group_summary": group_summary,
        "product_summary": product_summary,
        "screen_map": DB_STATUS_SCREEN_MAP,
        "notes": [
            "members / seat_positions / seat_maps 는 현재 운영 상태를 나타냅니다.",
            "member_versions / seat_assignment_versions / history_revisions 는 시점 조회와 변경 이력을 위한 테이블입니다.",
            "integration_raw_* / integration_* 는 원본 적재와 표준화 결과를 분리해서 보관합니다.",
            "integration_binary_sources 는 사업관리대장 같은 바이너리 원본 보관용입니다.",
            "DB를 물리적으로 합치기보다, 화면/권한/이력/로우데이터 관점으로 묶어 보는 것이 현재 운영에 더 적합합니다.",
            "재직 인원은 조직현황과 동일하게 work_status 값이 '퇴직'이 아닌 구성원 기준입니다.",
        ],
    }


def fetch_db_table_preview(schema_name: str, table_name: str, limit: int = 50) -> dict[str, object]:
    if schema_name not in {"public", "auth"}:
        raise HTTPException(status_code=404, detail="Unknown schema.")

    with get_conn() as conn:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT tablename
                FROM pg_tables
                WHERE schemaname = %s
                  AND tablename = %s
                """,
                (schema_name, table_name),
            )
            exists_row = cur.fetchone()
            if exists_row is None:
                raise HTTPException(status_code=404, detail="Unknown table.")

            table_ref = f"{schema_name}.{table_name}"
            spec = DB_STATUS_TABLE_META.get(table_ref, {})

            cur.execute(
                """
                SELECT column_name, data_type
                FROM information_schema.columns
                WHERE table_schema = %s
                  AND table_name = %s
                ORDER BY ordinal_position
                """,
                (schema_name, table_name),
            )
            columns = [{"name": str(row["column_name"]), "type": str(row["data_type"])} for row in cur.fetchall()]

            cur.execute(f"SELECT COUNT(*)::bigint AS row_count FROM {schema_name}.{table_name}")
            row_count = int((cur.fetchone() or {}).get("row_count") or 0)

            safe_limit = max(1, min(int(limit), 50))
            cur.execute(f"SELECT * FROM {schema_name}.{table_name} LIMIT {safe_limit}")
            rows = [make_json_safe(dict(row)) for row in cur.fetchall()]

    return {
        "table_ref": table_ref,
        "schema": schema_name,
        "table_name": table_name,
        "label": str(spec.get("label") or table_name),
        "domain": str(spec.get("domain") or "other"),
        "description": str(spec.get("description") or "세부 보조/원본/운영 테이블"),
        "related_views": spec.get("related_views") or [],
        "row_count": row_count,
        "limit": safe_limit,
        "columns": columns,
        "rows": rows,
    }
