"""PostgreSQL adapter for the approved booking Markdown fixture."""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from .md_fixture import (
    PROCESSOR_NAME,
    PROCESSOR_VERSION,
    RESULT_SCHEMA_VERSION,
    FixtureDocument,
    build_fixture_parse_result,
    canonical_json_bytes,
    parse_fixture_file,
    rule_set_sha256,
)


TARGET_DATABASE = "booking_test"
EXPECTED_FIXTURE_ROWS = 867
EXPECTED_FIXTURE_WORKSHEETS = 6
EXPECTED_FIXTURE_GROUP_CODES = 348
PROJECT_ROOT = Path(__file__).resolve().parents[1]


@dataclass(frozen=True)
class DatabaseConfig:
    host: str
    port: int
    user: str
    password: str
    database: str


@dataclass(frozen=True)
class ImportResult:
    status: str
    source_batch_id: int
    source_rows: int
    current_parses: int
    room_items: int
    distinct_group_codes: int


def _artifact_id(
    cursor,
    *,
    fixture_path: Path,
    content_sha256: str,
    byte_size: int,
) -> int:
    cursor.execute(
        """
        SELECT id, byte_size
        FROM ingestion.artifacts
        WHERE artifact_kind = 'booking_source_md'
          AND sha256 = %s
        """,
        (content_sha256,),
    )
    existing = cursor.fetchone()
    if existing is not None:
        if int(existing[1]) != byte_size:
            raise RuntimeError("existing booking artifact metadata conflicts")
        return int(existing[0])

    try:
        object_key = fixture_path.resolve().relative_to(PROJECT_ROOT).as_posix()
    except ValueError:
        raise RuntimeError("booking fixture must be inside the project root") from None
    cursor.execute(
        """
        INSERT INTO ingestion.artifacts (
            artifact_kind, storage_provider, bucket_alias, object_key,
            original_filename, sha256, byte_size, mime_type
        )
        VALUES (
            'booking_source_md', 'local_fixture', 'workspace', %s,
            %s, %s, %s, 'text/markdown'
        )
        RETURNING id
        """,
        (object_key, fixture_path.name, content_sha256, byte_size),
    )
    return int(cursor.fetchone()[0])


def _existing_result(
    cursor,
    *,
    artifact_id: int,
    expected: FixtureDocument,
) -> ImportResult | None:
    cursor.execute(
        """
        SELECT id, batch_status, source_rows, accepted_rows, failed_rows
        FROM booking.source_batches
        WHERE source_artifact_id = %s
        """,
        (artifact_id,),
    )
    batch = cursor.fetchone()
    if batch is None:
        return None
    batch_id = int(batch[0])
    if (
        batch[1] != "accepted"
        or int(batch[2]) != len(expected.rows)
        or int(batch[3]) != len(expected.rows)
        or int(batch[4]) != 0
    ):
        raise RuntimeError("existing booking source batch is incomplete")
    cursor.execute(
        """
        SELECT
            count(*) AS source_rows,
            count(current_parse.source_row_id) AS current_parses,
            count(item.id) AS room_items,
            count(DISTINCT source.group_code_key) AS distinct_group_codes
        FROM booking.source_rows AS source
        LEFT JOIN booking.current_row_parses AS current_parse
          ON current_parse.source_row_id = source.id
        LEFT JOIN booking.room_items AS item
          ON item.parse_version_id = current_parse.parse_version_id
        WHERE source.source_batch_id = %s
        """,
        (batch_id,),
    )
    counts = tuple(int(value) for value in cursor.fetchone())
    expected_counts = (
        len(expected.rows),
        len(expected.rows),
        len(expected.rows),
        expected.distinct_group_code_count,
    )
    if counts != expected_counts:
        raise RuntimeError("existing booking fixture rows are inconsistent")
    return ImportResult(
        status="already_applied_and_verified",
        source_batch_id=batch_id,
        source_rows=counts[0],
        current_parses=counts[1],
        room_items=counts[2],
        distinct_group_codes=counts[3],
    )


def import_fixture(
    config: DatabaseConfig,
    fixture_path: Path,
    contract_path: Path,
) -> ImportResult:
    if config.database != TARGET_DATABASE:
        raise RuntimeError("database target is invalid")
    fixture_path = fixture_path.resolve()
    contract_path = contract_path.resolve()
    document = parse_fixture_file(fixture_path)
    if (
        len(document.rows) != EXPECTED_FIXTURE_ROWS
        or document.worksheet_count != EXPECTED_FIXTURE_WORKSHEETS
        or document.distinct_group_code_count
        != EXPECTED_FIXTURE_GROUP_CODES
    ):
        raise RuntimeError("approved booking fixture baseline changed")

    fixture_bytes = fixture_path.read_bytes()
    fixture_sha256 = hashlib.sha256(fixture_bytes).hexdigest()
    rule_sha256 = rule_set_sha256(contract_path)
    run_key = f"booking-fixture:{fixture_sha256[:24]}"
    delivery_payload = {
        "source_kind": "expected_fixture",
        "source_rows": len(document.rows),
        "worksheet_count": document.worksheet_count,
        "distinct_group_codes": document.distinct_group_code_count,
        "source_file": document.source_file,
        "mapping_file": document.mapping_file,
        "contract": f"booking-row-parse-result/{RESULT_SCHEMA_VERSION}",
    }
    delivery_json = canonical_json_bytes(delivery_payload).decode("utf-8")
    delivery_sha256 = hashlib.sha256(
        delivery_json.encode("utf-8")
    ).hexdigest()

    try:
        import psycopg
    except ImportError:
        raise RuntimeError("psycopg is required for PostgreSQL import") from None

    connection = psycopg.connect(
        host=config.host,
        port=config.port,
        user=config.user,
        password=config.password,
        dbname=config.database,
        connect_timeout=5,
        options=(
            "-c statement_timeout=120000 "
            "-c lock_timeout=5000 "
            "-c idle_in_transaction_session_timeout=180000 "
            "-c application_name=arr_booking_fixture_import"
        ),
    )
    try:
        with connection.cursor() as cursor:
            cursor.execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
            cursor.execute(
                "SELECT current_database(), current_setting('transaction_read_only')"
            )
            if cursor.fetchone() != (TARGET_DATABASE, "off"):
                raise RuntimeError("database target/write guard failed")
            cursor.execute(
                "SELECT pg_advisory_xact_lock(hashtextextended(%s, 0))",
                ("arr_booking_fixture_import",),
            )
            cursor.execute("SELECT to_regclass('booking.source_rows')")
            if cursor.fetchone()[0] is None:
                raise RuntimeError("ARR MVP v1 booking schema is not installed")

            artifact_id = _artifact_id(
                cursor,
                fixture_path=fixture_path,
                content_sha256=fixture_sha256,
                byte_size=len(fixture_bytes),
            )
            existing = _existing_result(
                cursor,
                artifact_id=artifact_id,
                expected=document,
            )
            if existing is not None:
                connection.rollback()
                return existing

            cursor.execute(
                """
                INSERT INTO ingestion.processing_runs (
                    run_key, pipeline_type, source_artifact_id, run_status,
                    requested_processor_version, requested_rule_set_sha256,
                    delivered_processor_version, delivered_rule_set_sha256,
                    result_schema_version, delivery_sha256, delivery_json
                )
                VALUES (
                    %s, 'booking_source_import', %s, 'validating',
                    %s, %s, %s, %s, %s, %s, %s::jsonb
                )
                RETURNING id
                """,
                (
                    run_key,
                    artifact_id,
                    PROCESSOR_VERSION,
                    rule_sha256,
                    PROCESSOR_VERSION,
                    rule_sha256,
                    RESULT_SCHEMA_VERSION,
                    delivery_sha256,
                    delivery_json,
                ),
            )
            processing_run_id = int(cursor.fetchone()[0])
            cursor.execute(
                """
                INSERT INTO booking.source_batches (
                    source_artifact_id, source_kind, source_format_version,
                    batch_status, source_rows, accepted_rows, failed_rows
                )
                VALUES (
                    %s, 'expected_fixture', 'md/1.0',
                    'processing', %s, 0, 0
                )
                RETURNING id
                """,
                (artifact_id, len(document.rows)),
            )
            source_batch_id = int(cursor.fetchone()[0])

            for row in document.rows:
                row_sha256 = row.sha256()
                parse_result = build_fixture_parse_result(
                    row,
                    rule_sha256=rule_sha256,
                )
                result_bytes = canonical_json_bytes(parse_result)
                result_sha256 = hashlib.sha256(result_bytes).hexdigest()
                cursor.execute(
                    """
                    WITH source AS (
                        INSERT INTO booking.source_rows (
                            source_batch_id, source_worksheet, source_row_no,
                            group_code_raw, type_of_room_raw, no_of_rooms,
                            source_row_sha256
                        )
                        VALUES (%s, %s, %s, %s, %s, %s, %s)
                        RETURNING id
                    ),
                    parsed AS (
                        INSERT INTO booking.parse_versions (
                            source_row_id, version_no, parse_status,
                            result_schema_version, processor_name,
                            processor_version, rule_set_sha256,
                            input_row_sha256, result_sha256,
                            result_json, validated_at
                        )
                        SELECT
                            source.id, 1, 'accepted', %s, %s, %s, %s,
                            %s, %s, %s::jsonb, now()
                        FROM source
                        RETURNING id, source_row_id
                    ),
                    item AS (
                        INSERT INTO booking.room_items (
                            parse_version_id, item_no, room_type_raw,
                            room_type_code, quantity, unit_price,
                            currency_code, price_token_raw, source_fragment
                        )
                        SELECT
                            parsed.id, 1, %s, %s, %s,
                            NULL, NULL, NULL, %s
                        FROM parsed
                        RETURNING id
                    )
                    SELECT source_row_id, id
                    FROM parsed
                    """,
                    (
                        source_batch_id,
                        row.worksheet,
                        row.row_no,
                        row.group_code_raw,
                        row.type_of_room_raw,
                        row.no_of_rooms,
                        row_sha256,
                        RESULT_SCHEMA_VERSION,
                        PROCESSOR_NAME,
                        PROCESSOR_VERSION,
                        rule_sha256,
                        row_sha256,
                        result_sha256,
                        result_bytes.decode("utf-8"),
                        row.type_of_room_raw,
                        row.type_of_room_raw,
                        row.no_of_rooms,
                        row.type_of_room_raw,
                    ),
                )

            cursor.execute(
                """
                UPDATE booking.source_batches
                SET batch_status = 'accepted',
                    accepted_rows = source_rows,
                    failed_rows = 0,
                    validated_at = now(),
                    finished_at = now()
                WHERE id = %s
                """,
                (source_batch_id,),
            )
            cursor.execute(
                """
                INSERT INTO booking.current_row_parses (
                    source_row_id, parse_version_id
                )
                SELECT source.id, parsed.id
                FROM booking.source_rows AS source
                JOIN booking.parse_versions AS parsed
                  ON parsed.source_row_id = source.id
                 AND parsed.version_no = 1
                 AND parsed.parse_status = 'accepted'
                WHERE source.source_batch_id = %s
                """,
                (source_batch_id,),
            )
            cursor.execute(
                """
                UPDATE ingestion.processing_runs
                SET run_status = 'accepted',
                    updated_at = now(),
                    validated_at = now(),
                    finished_at = now()
                WHERE id = %s
                """,
                (processing_run_id,),
            )
            cursor.execute(
                """
                INSERT INTO ingestion.outbox_events (
                    event_key, aggregate_type, aggregate_id,
                    event_type, payload
                )
                VALUES (
                    %s, 'booking_source_batch', %s,
                    'booking.source_batch.accepted', %s::jsonb
                )
                """,
                (
                    f"booking-source-batch:{source_batch_id}:accepted",
                    source_batch_id,
                    json.dumps(
                        {
                            "source_batch_id": source_batch_id,
                            "source_rows": len(document.rows),
                            "distinct_group_codes":
                                document.distinct_group_code_count,
                        },
                        separators=(",", ":"),
                    ),
                ),
            )
            result = _existing_result(
                cursor,
                artifact_id=artifact_id,
                expected=document,
            )
            if result is None:
                raise RuntimeError("booking fixture post-write validation failed")
        connection.commit()
    except Exception:
        connection.rollback()
        raise
    finally:
        connection.close()

    return ImportResult(
        status="imported_and_verified",
        source_batch_id=result.source_batch_id,
        source_rows=result.source_rows,
        current_parses=result.current_parses,
        room_items=result.room_items,
        distinct_group_codes=result.distinct_group_codes,
    )
