from __future__ import annotations

import os
from dataclasses import dataclass

import pymysql


@dataclass(frozen=True)
class DbConfig:
    host: str = os.getenv("NAVSEA_DB_HOST", "localhost")
    port: int = int(os.getenv("NAVSEA_DB_PORT", "3306"))
    user: str = os.getenv("NAVSEA_DB_USER", "root")
    password: str = os.getenv("NAVSEA_DB_PASSWORD", "2chi9ks2")
    database: str = os.getenv("NAVSEA_DB_NAME", "pbf_analysis")
    unix_socket: str | None = os.getenv("NAVSEA_DB_SOCKET", "/tmp/mysql.sock")
    source_table: str = os.getenv("NAVSEA_GEOMETRY_SOURCE_TABLE", "feature_geometry_stage")


class GeometryEnablement:
    def __init__(self, config: DbConfig) -> None:
        self.config = config

    def connect(self):
        kwargs = {
            "host": self.config.host,
            "port": self.config.port,
            "user": self.config.user,
            "password": self.config.password,
            "database": self.config.database,
            "charset": "utf8mb4",
            "autocommit": False,
        }
        if self.config.unix_socket and self.config.host in {"localhost", "127.0.0.1"}:
            kwargs["unix_socket"] = self.config.unix_socket
        return pymysql.connect(**kwargs)

    def run(self) -> None:
        with self.connect() as conn:
            with conn.cursor() as cur:
                source_mode = self.detect_source_mode(cur)
                self.create_geometry_table(cur, source_mode)
                self.create_spatial_view(cur)
            conn.commit()
        print("Built navsea_feature_geometry and navsea_detection_objects_spatial")

    def detect_source_mode(self, cur) -> str:
        cur.execute("SHOW TABLES LIKE %s", (self.config.source_table,))
        if cur.fetchone() is None:
            raise RuntimeError(
                "geometry staging table missing: "
                f"{self.config.source_table}. "
                "Expected one of: "
                "(feature_id, geometry_wkt), "
                "(feature_id, geometry_wkb), "
                "(feature_id, lon, lat), "
                "or (feature_id, geometry GEOMETRY)."
            )

        cur.execute(
            """
            SELECT COLUMN_NAME, DATA_TYPE
            FROM information_schema.COLUMNS
            WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s
            """,
            (self.config.database, self.config.source_table),
        )
        columns = {name: data_type for name, data_type in cur.fetchall()}

        if "feature_id" not in columns:
            raise RuntimeError(f"{self.config.source_table} missing required column: feature_id")

        if "geometry" in columns and columns["geometry"] == "geometry":
            return "geometry"
        if "geometry_wkt" in columns:
            return "geometry_wkt"
        if "geometry_wkb" in columns:
            return "geometry_wkb"
        if {"lon", "lat"}.issubset(columns):
            return "lonlat"

        raise RuntimeError(
            f"{self.config.source_table} does not expose a supported geometry source. "
            "Supported layouts: geometry GEOMETRY, geometry_wkt, geometry_wkb, or lon/lat."
        )

    def create_geometry_table(self, cur, source_mode: str) -> None:
        cur.execute("DROP TABLE IF EXISTS navsea_feature_geometry")
        cur.execute(
            """
            CREATE TABLE navsea_feature_geometry (
                feature_id BIGINT NOT NULL,
                geometry GEOMETRY NOT NULL,
                geometry_source VARCHAR(32) NOT NULL,
                PRIMARY KEY (feature_id),
                SPATIAL KEY idx_geometry (geometry)
            ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
            """
        )

        source_table = self.config.source_table
        if source_mode == "geometry":
            cur.execute(
                f"""
                INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
                SELECT feature_id, geometry, 'geometry'
                FROM {source_table}
                """
            )
        elif source_mode == "geometry_wkt":
            cur.execute(
                f"""
                INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
                SELECT feature_id, GeomFromText(geometry_wkt), 'geometry_wkt'
                FROM {source_table}
                WHERE geometry_wkt IS NOT NULL AND geometry_wkt <> ''
                """
            )
        elif source_mode == "geometry_wkb":
            cur.execute(
                f"""
                INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
                SELECT feature_id, GeomFromWKB(geometry_wkb), 'geometry_wkb'
                FROM {source_table}
                WHERE geometry_wkb IS NOT NULL
                """
            )
        elif source_mode == "lonlat":
            cur.execute(
                f"""
                INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
                SELECT feature_id, Point(lon, lat), 'lonlat'
                FROM {source_table}
                WHERE lon IS NOT NULL AND lat IS NOT NULL
                """
            )
        else:
            raise RuntimeError(f"unsupported source mode: {source_mode}")

    @staticmethod
    def create_spatial_view(cur) -> None:
        cur.execute("DROP VIEW IF EXISTS navsea_detection_objects_spatial")
        cur.execute(
            """
            CREATE VIEW navsea_detection_objects_spatial AS
            SELECT
                d.feature_id,
                g.geometry,
                d.z,
                d.x,
                d.y,
                d.geom_type,
                d.source_layer,
                d.class_name,
                d.shape_name,
                d.layer_name,
                d.canonical_object_type,
                d.object_family,
                d.detection_class,
                d.capability,
                d.detection_key
            FROM navsea_detection_objects d
            JOIN navsea_feature_geometry g
                ON d.feature_id = g.feature_id
            """
        )


def main() -> None:
    GeometryEnablement(DbConfig()).run()


if __name__ == "__main__":
    main()
