import sqlite3
from typing import Any, List
from datetime import date, datetime
import uuid

from .base import DatabaseBackend
from atomsql.orm.fields import (
    IntegerField,
    StringField,
    CharField,
    TextField,
    ForeignKey,
    FloatField,
    DateField,
    DateTimeField,
    DateTimeTZField,
    TimeStampField,
    BooleanField,
    EmailField,
    URLField,
    JSONField,
    UUIDField,
    ManyToManyField,
)


class SQLiteBackend(DatabaseBackend):
    def __init__(self, db_path: str) -> None:
        self.db_path = db_path
        self.connection = None
        self.cursor = None

    @property
    def placeholder_char(self) -> str:
        return "?"

    def connect(self, **kwargs: Any) -> None:
        target = self.db_path if self.db_path else ":memory:"
        kwargs.setdefault("check_same_thread", False)
        self.connection = sqlite3.connect(target, **kwargs)
        self.cursor = self.connection.cursor()

    def disconnect(self) -> None:
        self.connection.close()

    def commit(self) -> None:
        self.connection.commit()

    def rollback(self) -> None:
        self.connection.rollback()

    def close(self) -> None:
        self.connection.close()

    def get_bulk_insert_ids(self, cursor, num_records: int) -> List[int]:
        """
        Extract inserted IDs after a bulk insert for SQLite.
        SQLite's lastrowid gives the last inserted ID, so we calculate backwards.
        """
        if hasattr(cursor, "lastrowid") and cursor.lastrowid:
            # SQLite's lastrowid gives us the last inserted ID
            # For multiple inserts, calculate the first ID
            last_id = cursor.lastrowid
            first_id = last_id - num_records + 1
            return list(range(first_id, last_id + 1))

        # Fallback if no lastrowid (shouldn't happen for normal inserts)
        return [None] * num_records

    def bulk_insert(self, sql: str, values: List[Any], num_records: int) -> List[int]:
        """
        Execute bulk insert for SQLite and return inserted IDs.
        SQLite's lastrowid gives the last inserted ID, so we calculate backwards.
        """
        cursor = self.execute(sql, values)
        return self.get_bulk_insert_ids(cursor, num_records)

    def get_field_type(self, field) -> str:
        if isinstance(field, BooleanField):
            return "INTEGER"
        elif isinstance(field, TimeStampField):
            return "INTEGER"
        elif isinstance(field, IntegerField):
            return "INTEGER"
        elif isinstance(field, CharField):
            return f"VARCHAR({field.max_length})"
        elif isinstance(field, (TextField, EmailField, URLField, StringField)):
            return "TEXT"
        elif isinstance(field, ForeignKey):
            return f'INTEGER REFERENCES "{field.to._table_name}"(id)'
        elif isinstance(field, ManyToManyField):
            # ManyToManyField doesn't have a direct column type
            return None
        elif isinstance(field, FloatField):
            return "REAL"
        elif isinstance(field, DateField):
            return "TEXT"
        elif isinstance(field, DateTimeField):
            return "TEXT"
        elif isinstance(field, DateTimeTZField):
            return "TEXT"
        elif isinstance(field, JSONField):
            return "TEXT"
        elif isinstance(field, UUIDField):
            return "TEXT"
        return "TEXT"

    def get_primary_key_constraint(self, column_name: str, field_type: str) -> str:
        return f'"{column_name}" {field_type} PRIMARY KEY AUTOINCREMENT'

    # Inherits get_check_constraint from base class - SQLite supports CHECK constraints

    def get_tables(self, exclude_migrations: bool = False) -> list:
        """Get list of tables in database."""
        cursor = self.connection.cursor()
        cursor.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
        )
        tables = [row[0] for row in cursor.fetchall()]

        if exclude_migrations:
            tables = [t for t in tables if t != "_migrations"]

        return tables

    def get_table_columns(self, table_name: str) -> list:
        """Get columns for a table."""
        cursor = self.connection.cursor()
        cursor.execute(f"PRAGMA table_info({table_name})")
        return cursor.fetchall()

    def prepare_value(self, value: Any) -> Any:
        """Prepare a value for insertion into the database."""
        # Check datetime first since it's a subclass of date
        if isinstance(value, (dict, list)):
            import json

            return json.dumps(value)
        if isinstance(value, bool):
            return 1 if value else 0
        if isinstance(value, datetime):
            # Use space separator for consistency with how datetime.__str__ formats it
            # This ensures filtering works correctly
            return value.isoformat(" ")
        if isinstance(value, date):
            return value.isoformat()
        if isinstance(value, uuid.UUID):
            return str(value)
        return value

    def get_json_extract_sql(
        self, column: str, path: List[str], cast_type: str = None
    ) -> str:
        """
        SQLite JSON extraction using json_extract.
        """
        if not path:
            return column

        # build path string $.key1.key2
        # We quote keys to handle special characters or dots
        json_path = "$." + ".".join([f'"{p}"' for p in path])

        sql = f"json_extract({column}, '{json_path}')"

        if cast_type:
            sql = f"CAST({sql} as {cast_type})"

        return sql
