import json
import sqlite3
from typing import Any, Dict, List, Optional, Union

from loguru import logger

from leggen.repositories.db import db_exists, get_db_connection


class TransactionRepository:
    """Repository for transaction data operations"""

    def _build_filter_clause(
        self,
        account_id: Optional[str] = None,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
        min_amount: Optional[float] = None,
        max_amount: Optional[float] = None,
        search: Optional[str] = None,
        category_id: Optional[str] = None,
    ) -> tuple[str, List[Union[str, int, float]]]:
        """Build WHERE clause and params for transaction filtering."""
        clause = ""
        params: List[Union[str, int, float]] = []

        if account_id:
            clause += " AND t.accountId = ?"
            params.append(account_id)

        if date_from:
            clause += " AND t.transactionDate >= ?"
            params.append(date_from)

        if date_to:
            clause += " AND t.transactionDate <= ?"
            params.append(date_to)

        if min_amount is not None:
            clause += " AND t.transactionValue >= ?"
            params.append(min_amount)

        if max_amount is not None:
            clause += " AND t.transactionValue <= ?"
            params.append(max_amount)

        if search:
            clause += " AND t.description LIKE ?"
            params.append(f"%{search}%")

        if category_id:
            if category_id == "uncategorized":
                clause += " AND tc.categoryId IS NULL"
            else:
                clause += " AND tc.categoryId = ?"
                params.append(int(category_id))

        return clause, params

    def create_table(self):
        """Create transactions table with indexes"""
        with get_db_connection() as conn:
            cursor = conn.cursor()

            cursor.execute(
                """CREATE TABLE IF NOT EXISTS transactions (
                accountId TEXT NOT NULL,
                transactionId TEXT NOT NULL,
                internalTransactionId TEXT,
                institutionId TEXT,
                iban TEXT,
                transactionDate DATETIME,
                description TEXT,
                transactionValue REAL,
                transactionCurrency TEXT,
                transactionStatus TEXT,
                rawTransaction JSON,
                PRIMARY KEY (accountId, transactionId)
            )"""
            )

            # Create indexes for better performance
            cursor.execute(
                """CREATE INDEX IF NOT EXISTS idx_transactions_internal_id
                   ON transactions(internalTransactionId)"""
            )
            cursor.execute(
                """CREATE INDEX IF NOT EXISTS idx_transactions_date
                   ON transactions(transactionDate)"""
            )
            cursor.execute(
                """CREATE INDEX IF NOT EXISTS idx_transactions_account_date
                   ON transactions(accountId, transactionDate)"""
            )
            cursor.execute(
                """CREATE INDEX IF NOT EXISTS idx_transactions_amount
                   ON transactions(transactionValue)"""
            )

            conn.commit()

    def persist(
        self, account_id: str, transactions: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Persist transactions to database, return new ones"""
        try:
            with get_db_connection() as conn:
                cursor = conn.cursor()

                insert_sql = """INSERT OR REPLACE INTO transactions (
                    accountId,
                    transactionId,
                    internalTransactionId,
                    institutionId,
                    iban,
                    transactionDate,
                    description,
                    transactionValue,
                    transactionCurrency,
                    transactionStatus,
                    rawTransaction
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""

                new_transactions = []

                for transaction in transactions:
                    try:
                        # Check if transaction already exists
                        cursor.execute(
                            """SELECT COUNT(*) FROM transactions
                               WHERE accountId = ? AND transactionId = ?""",
                            (transaction["accountId"], transaction["transactionId"]),
                        )
                        exists = cursor.fetchone()[0] > 0

                        cursor.execute(
                            insert_sql,
                            (
                                transaction["accountId"],
                                transaction["transactionId"],
                                transaction.get("internalTransactionId"),
                                transaction["institutionId"],
                                transaction["iban"],
                                transaction["transactionDate"],
                                transaction["description"],
                                transaction["transactionValue"],
                                transaction["transactionCurrency"],
                                transaction["transactionStatus"],
                                json.dumps(transaction["rawTransaction"]),
                            ),
                        )

                        if not exists:
                            new_transactions.append(transaction)

                    except sqlite3.IntegrityError as e:
                        logger.warning(
                            f"Failed to insert transaction {transaction.get('transactionId')}: {e}"
                        )
                        continue

                conn.commit()

            logger.info(
                f"Persisted {len(new_transactions)} new transactions for account {account_id}"
            )
            return new_transactions
        except Exception as e:
            logger.error(f"Failed to persist transactions: {e}")
            raise

    def get_transactions(
        self,
        account_id: Optional[str] = None,
        limit: Optional[int] = 100,
        offset: int = 0,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
        min_amount: Optional[float] = None,
        max_amount: Optional[float] = None,
        search: Optional[str] = None,
        category_id: Optional[str] = None,
    ) -> List[Dict[str, Any]]:
        """Get transactions with optional filtering"""
        if not db_exists():
            return []

        with get_db_connection(row_factory=True) as conn:
            cursor = conn.cursor()

            filter_clause, params = self._build_filter_clause(
                account_id=account_id,
                date_from=date_from,
                date_to=date_to,
                min_amount=min_amount,
                max_amount=max_amount,
                search=search,
                category_id=category_id,
            )

            query = (
                """SELECT t.*, tc.categoryId, c.name as categoryName, c.color as categoryColor
                FROM transactions t
                LEFT JOIN transaction_categories tc ON t.accountId = tc.accountId AND t.transactionId = tc.transactionId
                LEFT JOIN categories c ON tc.categoryId = c.id
                WHERE 1=1"""
                + filter_clause
            )
            query += " ORDER BY t.transactionDate DESC"

            if limit:
                query += " LIMIT ?"
                params.append(limit)

            if offset:
                query += " OFFSET ?"
                params.append(offset)

            cursor.execute(query, params)
            rows = cursor.fetchall()

            transactions = []
            for row in rows:
                transaction = dict(row)
                if transaction["rawTransaction"]:
                    transaction["rawTransaction"] = json.loads(
                        transaction["rawTransaction"]
                    )
                transactions.append(transaction)

            return transactions

    def get_count(
        self,
        account_id: Optional[str] = None,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
        min_amount: Optional[float] = None,
        max_amount: Optional[float] = None,
        search: Optional[str] = None,
        category_id: Optional[str] = None,
    ) -> int:
        """Get total count of transactions matching filters"""
        if not db_exists():
            return 0

        with get_db_connection() as conn:
            cursor = conn.cursor()

            filter_clause, params = self._build_filter_clause(
                account_id=account_id,
                date_from=date_from,
                date_to=date_to,
                min_amount=min_amount,
                max_amount=max_amount,
                search=search,
                category_id=category_id,
            )

            query = (
                """SELECT COUNT(*) FROM transactions t
                LEFT JOIN transaction_categories tc ON t.accountId = tc.accountId AND t.transactionId = tc.transactionId
                LEFT JOIN categories c ON tc.categoryId = c.id
                WHERE 1=1"""
                + filter_clause
            )
            cursor.execute(query, params)
            return cursor.fetchone()[0]

    def get_transaction_by_id(
        self, account_id: str, transaction_id: str
    ) -> Optional[Dict[str, Any]]:
        """Get a single transaction by its primary key."""
        if not db_exists():
            return None

        with get_db_connection(row_factory=True) as conn:
            cursor = conn.cursor()
            cursor.execute(
                """SELECT t.*, tc.categoryId, c.name as categoryName, c.color as categoryColor
                FROM transactions t
                LEFT JOIN transaction_categories tc ON t.accountId = tc.accountId AND t.transactionId = tc.transactionId
                LEFT JOIN categories c ON tc.categoryId = c.id
                WHERE t.accountId = ? AND t.transactionId = ?""",
                (account_id, transaction_id),
            )
            row = cursor.fetchone()
            if row:
                transaction = dict(row)
                if transaction["rawTransaction"]:
                    transaction["rawTransaction"] = json.loads(
                        transaction["rawTransaction"]
                    )
                return transaction
            return None

    def get_account_summary(self, account_id: str) -> Optional[Dict[str, Any]]:
        """Get basic account info from transactions table"""
        if not db_exists():
            return None

        with get_db_connection(row_factory=True) as conn:
            cursor = conn.cursor()

            cursor.execute(
                """
                SELECT DISTINCT accountId, institutionId, iban
                FROM transactions
                WHERE accountId = ?
                ORDER BY transactionDate DESC
                LIMIT 1
            """,
                (account_id,),
            )

            row = cursor.fetchone()
            if row:
                return dict(row)
            return None
