"""Service for syncing nomenclatures from Oracle to SQLite."""

import sys
from pathlib import Path
from typing import Optional, List, Tuple
from datetime import datetime
import logging

from sqlmodel import select
from sqlalchemy.ext.asyncio import AsyncSession

# Path setup handled by main.py - this is redundant
# project_root = Path(__file__).parent.parent.parent.parent.parent
# sys.path.insert(0, str(project_root / "shared"))

from shared.database.oracle_pool import oracle_pool
from backend.modules.data_entry.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister

logger = logging.getLogger(__name__)

# Cache for schema lookups (populated dynamically from Oracle)
_schema_cache: dict[int, str] = {}


class SyncService:
    """Service for syncing nomenclatures from Oracle."""

    @staticmethod
    async def get_schema_for_company(company_id: int) -> Optional[str]:
        """
        Get Oracle schema for company ID from V_NOM_FIRME view.
        Results are cached in memory for performance.
        """
        # Check cache first
        if company_id in _schema_cache:
            return _schema_cache[company_id]

        try:
            async with oracle_pool.get_connection() as connection:
                with connection.cursor() as cursor:
                    cursor.execute("""
                        SELECT SCHEMA
                        FROM CONTAFIN_ORACLE.V_NOM_FIRME
                        WHERE ID_FIRMA = :company_id
                    """, {'company_id': company_id})
                    result = cursor.fetchone()

                    if result:
                        schema = result[0]
                        _schema_cache[company_id] = schema
                        logger.info(f"Resolved schema for company {company_id}: {schema}")
                        return schema
                    else:
                        logger.warning(f"No schema found for company {company_id}")
                        return None

        except Exception as e:
            logger.error(f"Error fetching schema for company {company_id}: {e}")
            return None

    @staticmethod
    async def sync_suppliers(session: AsyncSession, company_id: int) -> Tuple[int, int]:
        """
        Sync suppliers (furnizori, id_tip_part=17) from Oracle to SQLite.
        Uses CORESP_TIP_PART joined with VNOM_PARTENERI view.
        Returns (synced_count, error_count).
        """
        schema = await SyncService.get_schema_for_company(company_id)
        if not schema:
            logger.warning(f"No schema mapping for company {company_id}")
            return 0, 0

        synced = 0
        errors = 0

        try:
            async with oracle_pool.get_connection() as connection:
                with connection.cursor() as cursor:
                    # Fetch active suppliers from Oracle
                    # id_tip_part = 17 means "furnizori" (suppliers)
                    # Using CORESP_TIP_PART to filter by partner type
                    cursor.execute(f"""
                        SELECT B.ID_PART, B.DENUMIRE, B.COD_FISCAL, B.ADRESA
                        FROM {schema}.CORESP_TIP_PART A
                        INNER JOIN {schema}.VNOM_PARTENERI B ON A.ID_PART = B.ID_PART
                        WHERE A.ID_TIP_PART = 17
                          AND (B.INACTIV = 0 OR B.INACTIV IS NULL)
                          AND B.ID_PART IS NOT NULL
                        ORDER BY B.DENUMIRE
                    """)
                    rows = cursor.fetchall()

                    for row in rows:
                        try:
                            oracle_id, name, fiscal_code, address = row

                            # Check if already exists
                            stmt = select(SyncedSupplier).where(
                                SyncedSupplier.oracle_id == oracle_id,
                                SyncedSupplier.company_id == company_id
                            )
                            result = await session.execute(stmt)
                            existing = result.scalar_one_or_none()

                            if existing:
                                # Update existing record
                                existing.name = name or ""
                                existing.fiscal_code = fiscal_code
                                existing.address = address
                                existing.synced_at = datetime.utcnow()
                                logger.debug(f"Updated supplier {oracle_id}: {name}")
                            else:
                                # Create new record
                                supplier = SyncedSupplier(
                                    oracle_id=oracle_id,
                                    company_id=company_id,
                                    name=name or "",
                                    fiscal_code=fiscal_code,
                                    address=address,
                                )
                                session.add(supplier)
                                logger.debug(f"Created supplier {oracle_id}: {name}")

                            synced += 1

                        except Exception as e:
                            logger.error(f"Error processing supplier row {row}: {e}")
                            errors += 1

                    # Commit all changes
                    await session.commit()
                    logger.info(f"Synced {synced} suppliers for company {company_id}, {errors} errors")

        except Exception as e:
            logger.error(f"Error syncing suppliers for company {company_id}: {e}")
            errors += 1
            await session.rollback()

        return synced, errors

    @staticmethod
    async def sync_cash_registers(session: AsyncSession, company_id: int) -> Tuple[int, int]:
        """
        Sync cash registers and bank accounts from Oracle to SQLite.
        Returns (synced_count, error_count).

        Uses CORESP_TIP_PART with:
        - id_tip_part = 22: CASA LEI
        - id_tip_part = 23: CASA VALUTA
        - id_tip_part = 24: BANCA LEI
        - id_tip_part = 25: BANCA VALUTA
        """
        schema = await SyncService.get_schema_for_company(company_id)
        if not schema:
            logger.warning(f"No schema mapping for company {company_id}")
            return 0, 0

        synced = 0
        errors = 0

        # Partner types mapping
        # 22=CASA LEI, 23=CASA VALUTA -> cash
        # 24=BANCA LEI, 25=BANCA VALUTA -> bank
        partner_types = [22, 23, 24, 25]

        try:
            async with oracle_pool.get_connection() as connection:
                with connection.cursor() as cursor:
                    # Fetch cash/bank partners from CORESP_TIP_PART
                    cursor.execute(f"""
                        SELECT B.ID_PART, B.DENUMIRE, A.ID_TIP_PART
                        FROM {schema}.CORESP_TIP_PART A
                        INNER JOIN {schema}.VNOM_PARTENERI B ON A.ID_PART = B.ID_PART
                        WHERE A.ID_TIP_PART IN (22, 23, 24, 25)
                          AND (B.INACTIV = 0 OR B.INACTIV IS NULL)
                          AND B.ID_PART IS NOT NULL
                        ORDER BY A.ID_TIP_PART, B.DENUMIRE
                    """)
                    rows = cursor.fetchall()

                    # Type mapping: 22=CASA LEI, 23=CASA VALUTA -> cash; 24=BANCA LEI, 25=BANCA VALUTA -> bank
                    type_mapping = {
                        22: ("cash", "CASA_LEI"),
                        23: ("cash", "CASA_VALUTA"),
                        24: ("bank", "BANCA_LEI"),
                        25: ("bank", "BANCA_VALUTA"),
                    }

                    for row in rows:
                        try:
                            oracle_id, name, tip_part_id = row

                            # Determine type based on partner type
                            register_type, account_code = type_mapping.get(tip_part_id, ("cash", "UNKNOWN"))

                            # Check if already exists
                            stmt = select(SyncedCashRegister).where(
                                SyncedCashRegister.oracle_id == oracle_id,
                                SyncedCashRegister.company_id == company_id
                            )
                            result = await session.execute(stmt)
                            existing = result.scalar_one_or_none()

                            if existing:
                                # Update existing record
                                existing.name = name or ""
                                existing.account_code = account_code
                                existing.register_type = register_type
                                existing.synced_at = datetime.utcnow()
                                logger.debug(f"Updated cash register {oracle_id}: {name}")
                            else:
                                # Create new record
                                cash_register = SyncedCashRegister(
                                    oracle_id=oracle_id,
                                    company_id=company_id,
                                    name=name or "",
                                    account_code=account_code,
                                    register_type=register_type,
                                )
                                session.add(cash_register)
                                logger.debug(f"Created cash register {oracle_id}: {name}")

                            synced += 1

                        except Exception as e:
                            logger.error(f"Error processing cash register row {row}: {e}")
                            errors += 1

                    # Commit all changes
                    await session.commit()
                    logger.info(f"Synced {synced} cash registers for company {company_id}, {errors} errors")

        except Exception as e:
            logger.error(f"Error syncing cash registers for company {company_id}: {e}")
            errors += 1
            await session.rollback()

        return synced, errors

    @staticmethod
    def _get_fiscal_code_variants(fiscal_code: str) -> list:
        """
        Generate all possible variants of a Romanian fiscal code (CUI).
        Database may store: "22891860", "RO22891860", "RO 22891860"
        OCR may extract: "RO22891860" or "22891860"
        """
        import re
        # Extract just the digits
        digits = re.sub(r'[^0-9]', '', fiscal_code)
        if not digits:
            return [fiscal_code]

        # Generate all variants
        variants = [
            digits,                # Just digits: 22891860
            f"RO{digits}",         # With RO prefix: RO22891860
            f"RO {digits}",        # With RO prefix and space: RO 22891860
        ]
        # Also add the original if different
        if fiscal_code not in variants:
            variants.append(fiscal_code)

        return variants

    @staticmethod
    async def search_supplier(
        session: AsyncSession,
        company_id: int,
        fiscal_code: Optional[str] = None,
        name: Optional[str] = None
    ) -> Tuple[bool, Optional[dict], str]:
        """
        Search for supplier in SQLite first, then Oracle if not found.
        Returns (found, supplier_data, source).
        Source can be: 'synced', 'local', 'not_found'
        """
        # 1. Search in synced suppliers
        if fiscal_code:
            # Search all variants of the fiscal code (with/without RO, with/without space)
            variants = SyncService._get_fiscal_code_variants(fiscal_code)
            stmt = select(SyncedSupplier).where(
                SyncedSupplier.company_id == company_id,
                SyncedSupplier.fiscal_code.in_(variants)
            )
        elif name:
            stmt = select(SyncedSupplier).where(
                SyncedSupplier.company_id == company_id,
                SyncedSupplier.name.ilike(f"%{name}%")
            )
        else:
            return False, None, "no_query"

        result = await session.execute(stmt)
        supplier = result.scalar_one_or_none()

        if supplier:
            # Return only text data - no IDs needed for autocomplete
            return True, {
                "name": supplier.name,
                "fiscal_code": supplier.fiscal_code,
                "address": supplier.address,
            }, "synced"

        # 2. Search in local suppliers
        if fiscal_code:
            # Search all variants of the fiscal code (with/without RO, with/without space)
            variants = SyncService._get_fiscal_code_variants(fiscal_code)
            stmt = select(LocalSupplier).where(
                LocalSupplier.company_id == company_id,
                LocalSupplier.fiscal_code.in_(variants)
            )
        elif name:
            stmt = select(LocalSupplier).where(
                LocalSupplier.company_id == company_id,
                LocalSupplier.name.ilike(f"%{name}%")
            )

        result = await session.execute(stmt)
        local = result.scalar_one_or_none()

        if local:
            # Return only text data - no IDs needed for autocomplete
            return True, {
                "name": local.name,
                "fiscal_code": local.fiscal_code,
                "address": local.address,
            }, "local"

        # 3. Try live Oracle search (optional fallback for unsynced data)
        # This is a fallback - ideally sync should be up to date
        # TODO: Implement live Oracle search if needed

        return False, None, "not_found"

    @staticmethod
    async def create_local_supplier(
        session: AsyncSession,
        company_id: int,
        name: str,
        fiscal_code: Optional[str],
        address: Optional[str],
        created_by: str
    ) -> LocalSupplier:
        """Create a local supplier entry from OCR data."""
        supplier = LocalSupplier(
            company_id=company_id,
            name=name,
            fiscal_code=fiscal_code,
            address=address,
            created_by=created_by,
        )
        session.add(supplier)
        await session.commit()
        await session.refresh(supplier)
        logger.info(f"Created local supplier: {name} (CUI: {fiscal_code})")
        return supplier

    @staticmethod
    async def get_all_suppliers(
        session: AsyncSession,
        company_id: int,
        search: Optional[str] = None
    ) -> List[dict]:
        """
        Get all suppliers (synced + local) for a company.
        Used for dropdown/autocomplete in UI.
        """
        suppliers = []

        # Get synced suppliers
        stmt = select(SyncedSupplier).where(SyncedSupplier.company_id == company_id)
        if search:
            stmt = stmt.where(
                (SyncedSupplier.name.ilike(f"%{search}%")) |
                (SyncedSupplier.fiscal_code.ilike(f"%{search}%"))
            )
        stmt = stmt.limit(50)  # Limit results for performance

        result = await session.execute(stmt)
        synced = result.scalars().all()

        for s in synced:
            suppliers.append({
                "id": s.id,
                "oracle_id": s.oracle_id,
                "name": s.name,
                "fiscal_code": s.fiscal_code,
                "source": "synced"
            })

        # Get local suppliers
        stmt = select(LocalSupplier).where(LocalSupplier.company_id == company_id)
        if search:
            stmt = stmt.where(
                (LocalSupplier.name.ilike(f"%{search}%")) |
                (LocalSupplier.fiscal_code.ilike(f"%{search}%"))
            )
        stmt = stmt.limit(50)

        result = await session.execute(stmt)
        local = result.scalars().all()

        for l in local:
            suppliers.append({
                "id": l.id,
                "name": l.name,
                "fiscal_code": l.fiscal_code,
                "source": "local"
            })

        return suppliers

    @staticmethod
    async def get_all_cash_registers(
        session: AsyncSession,
        company_id: int
    ) -> List[dict]:
        """
        Get all cash registers for a company.
        Used for dropdown in UI.
        """
        stmt = select(SyncedCashRegister).where(SyncedCashRegister.company_id == company_id)
        result = await session.execute(stmt)
        registers = result.scalars().all()

        return [
            {
                "id": r.id,
                "oracle_id": r.oracle_id,
                "name": r.name,
                "account_code": r.account_code,
                "register_type": r.register_type
            }
            for r in registers
        ]
