"""
simPRO API client for fetching customer invoices.
Ports logic from src/services/simproApi.js to Python.
"""
import asyncio
import logging
from datetime import date, datetime, timezone
from typing import List, Dict, Optional, Tuple
from zoneinfo import ZoneInfo

import httpx

from app.config import settings

PERCH_TZ = ZoneInfo("Australia/Perth")

logger = logging.getLogger(__name__)

class SimProClient:
    """Client for interacting with simPRO API."""
    
    def __init__(self, branch_id: str):
        """
        Initialize client for a specific branch.
        
        Args:
            branch_id: Branch identifier (branch1, branch2, branch3)
        """
        self.branch_id = branch_id
        self.branch_config = settings.BRANCHES[branch_id]
        self.api_key = self.branch_config["api_key"]
        self.api_url = self.branch_config["api_url"]
        self.companies = self.branch_config["companies"]
        
    async def fetch_company_invoices(
        self,
        company_id: int,
        year: int = 2025,
        since_date: Optional[datetime] = None
    ) -> List[Dict]:
        """
        Fetch invoices for a specific company.
        
        Args:
            company_id: simPRO company ID
            year: Year to fetch invoices for
            since_date: Only fetch invoices after this date (for incremental updates)
            
        Returns:
            List of invoice dictionaries
        """
        all_invoices = []
        page = 1
        has_more_data = True
        # Use naive datetime (no timezone) for consistent date comparisons
        oldest_date_of_interest = datetime(year, 1, 1)
        
        company_name = self.companies[company_id]["name"]
        logger.info(f"🔍 Fetching invoices for {self.branch_config['name']} - {company_name} (Company {company_id})")
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            while has_more_data:
                # Build API endpoint and parameters
                endpoint = f"{self.api_url}/companies/{company_id}/customerInvoices/"
                params = {
                    "columns": "ID,DateIssued,Total,IsPaid,Customer,Jobs,Stage,Status",
                    "orderby": "-DateIssued",
                    "pageSize": 250,
                    "page": page
                }
                
                headers = {
                    "Authorization": f"Bearer {self.api_key}",
                    "Content-Type": "application/json",
                    "Accept": "application/json"
                }
                
                try:
                    response = await client.get(endpoint, params=params, headers=headers)
                    response.raise_for_status()
                    data = response.json()
                    
                    # Handle both list and object-with-items formats
                    items = data if isinstance(data, list) else data.get("items", [])
                    
                    if not items:
                        has_more_data = False
                        break
                    
                    # Tag invoices with metadata
                    for invoice in items:
                        invoice["branch_id"] = self.branch_id
                        invoice["company_id"] = company_id
                        invoice["company_name"] = company_name
                    
                    # Check for early termination conditions
                    if items:
                        # Parse date as naive datetime (no timezone) for consistent comparison
                        oldest_invoice_date_str = items[-1]["DateIssued"].replace('Z', '+00:00')
                        oldest_invoice_date = datetime.fromisoformat(oldest_invoice_date_str).replace(tzinfo=None)
                        
                        # Stop if we've reached invoices before year start
                        if oldest_invoice_date < oldest_date_of_interest:
                            logger.info(f"✅ {company_name} reached old data ({oldest_invoice_date.date()}), stopping")
                            # Filter out invoices before year start - use naive datetimes
                            filtered_items = []
                            for inv in items:
                                inv_date_str = inv["DateIssued"].replace('Z', '+00:00')
                                inv_date = datetime.fromisoformat(inv_date_str).replace(tzinfo=None)
                                if inv_date >= oldest_date_of_interest:
                                    filtered_items.append(inv)
                            if filtered_items:
                                all_invoices.extend(filtered_items)
                            has_more_data = False
                            break
                        
                        # Stop if incremental and reached since_date
                        if since_date and oldest_invoice_date <= since_date:
                            logger.info(f"✅ {company_name} reached last sync date, stopping")
                            # Filter for only new invoices - use naive datetimes
                            filtered_items = []
                            for inv in items:
                                inv_date_str = inv["DateIssued"].replace('Z', '+00:00')
                                inv_date = datetime.fromisoformat(inv_date_str).replace(tzinfo=None)
                                if inv_date > since_date:
                                    filtered_items.append(inv)
                            if filtered_items:
                                all_invoices.extend(filtered_items)
                            has_more_data = False
                            break
                        
                        all_invoices.extend(items)
                        logger.info(f"📄 {company_name} page {page}: {len(items)} invoices (total: {len(all_invoices)})")
                        
                        # Check if last page
                        if len(items) < 250:
                            logger.info(f"✅ {company_name} reached last page")
                            has_more_data = False
                        else:
                            page += 1
                    else:
                        has_more_data = False
                        
                except httpx.HTTPError as e:
                    logger.error(f"❌ Error fetching {company_name} page {page}: {e}")
                    has_more_data = False
                    
                # Small delay to avoid rate limiting
                await asyncio.sleep(0.1)
        
        logger.info(f"📊 {company_name} fetch complete: {len(all_invoices)} invoices")
        return all_invoices

    @staticmethod
    def _is_stock_order(job: dict) -> bool:
        """Detect internal stock order jobs (rolling POs, not real customer jobs)."""
        site = job.get("Site", {})
        site_name = (site.get("Name", "") if isinstance(site, dict) else "").lower()
        job_name = (job.get("Name", "") or "").lower()
        return "stock order" in site_name or "stock order" in job_name

    async def fetch_wip_jobs_for_branch(self) -> dict:
        """
        Fetch WIP (Work in Progress) jobs for this branch via Simpro API.
        Queries Pending + Progress + Complete stage jobs across all companies.

        Filters applied:
          1. Exclude stock-order jobs (Site or Name contains "stock order")
          2. Only include jobs where Difference < 0 (direct costs exceed billed)

        WIP value = sum of |Difference| for negative-Difference jobs (excl stock orders).
        Difference = Billed Ex Tax - Direct Cost (labour + materials).
        Returns { job_count: int, wip_value: float } or empty dict on failure.
        """
        if not self.api_key:
            logger.warning("Simpro API key not configured for %s", self.branch_config["name"])
            return {}

        job_count = 0
        wip_value = 0.0
        departments = {}
        wip_stages = ["Pending", "Progress", "Complete"]
        page_size = 250
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

        async with httpx.AsyncClient(timeout=30.0) as client:
            for company_id, company_info in self.companies.items():
                company_name = company_info["name"]
                dept = departments.setdefault(company_name, {"job_count": 0, "wip_value": 0.0})
                for stage in wip_stages:
                    page = 1
                    while True:
                        try:
                            endpoint = f"{self.api_url}/companies/{company_id}/jobs/"
                            params = {
                                "columns": "ID,Name,Site,Total,Totals,Stage",
                                "pageSize": page_size,
                                "page": page,
                                "Stage": stage,
                            }
                            response = await client.get(endpoint, params=params, headers=headers)
                            if response.status_code == 401:
                                logger.warning("Simpro API 401 for %s - check API key", self.branch_config["name"])
                                return {}
                            if response.status_code == 404:
                                logger.warning("Simpro jobs endpoint 404 for %s", self.branch_config["name"])
                                return {}
                            response.raise_for_status()
                            items = response.json()
                            if not isinstance(items, list):
                                items = items.get("items") or items.get("values") or []
                            if not items:
                                break

                            for job in items:
                                if self._is_stock_order(job):
                                    continue

                                total_obj = job.get("Total", {})
                                total_ex = total_obj.get("ExTax", 0) if isinstance(total_obj, dict) else 0
                                totals_obj = job.get("Totals", {}) if isinstance(job.get("Totals"), dict) else {}
                                inv_pct = totals_obj.get("InvoicePercentage", 0)

                                billed_ex = total_ex * inv_pct / 100 if inv_pct else 0
                                res_cost = totals_obj.get("ResourcesCost", {}).get("Total", {}).get("Actual", 0)
                                mat_cost = totals_obj.get("MaterialsCost", {}).get("Actual", 0)
                                direct_cost = res_cost + mat_cost
                                difference = billed_ex - direct_cost

                                if difference >= 0:
                                    continue

                                abs_diff = abs(difference)
                                job_count += 1
                                wip_value += abs_diff
                                dept["job_count"] += 1
                                dept["wip_value"] += abs_diff

                            if len(items) < page_size:
                                break
                            page += 1
                            await asyncio.sleep(0.05)
                        except httpx.HTTPError as e:
                            logger.error("Simpro WIP fetch error %s/%s %s: %s", self.branch_config["name"], company_name, stage, e)
                            break
                        except Exception as e:
                            logger.exception("Simpro WIP unexpected error: %s", e)
                            break

        for dept in departments.values():
            dept["wip_value"] = round(dept["wip_value"], 2)

        result = {
            "job_count": job_count,
            "wip_value": round(wip_value, 2),
            "departments": departments,
        }
        logger.info("Simpro WIP %s: %d jobs, $%s WIP value (%d departments)",
                     self.branch_config["name"], job_count, f"{wip_value:,.2f}", len(departments))
        return result

    async def fetch_job_stats_for_company(
        self, company_id: int
    ) -> dict:
        """
        Fetch job stats for a single company (e.g. Bunbury Commercial):
        - jobs_completed: count of jobs with Stage=Complete
        - total_pending_jobs: count of jobs with Stage in Pending, Progress
        - pending_jobs_with_due_dates: of those pending, count with a non-empty DueDate

        Excludes stock-order jobs. Returns empty dict on API failure.
        """
        if not self.api_key:
            logger.warning("Simpro API key not configured for %s", self.branch_config["name"])
            return {}

        company_name = self.companies.get(company_id, {}).get("name", f"Company {company_id}")
        jobs_completed = 0
        total_pending_jobs = 0
        pending_jobs_with_due_dates = 0
        page_size = 250
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

        async with httpx.AsyncClient(timeout=30.0) as client:
            # ---- Complete: count only ----
            page = 1
            while True:
                try:
                    resp = await client.get(
                        f"{self.api_url}/companies/{company_id}/jobs/",
                        params={
                            "columns": "ID,Name,Site,Stage",
                            "pageSize": page_size,
                            "page": page,
                            "Stage": "Complete",
                        },
                        headers=headers,
                    )
                    if resp.status_code in (401, 404):
                        logger.warning("Simpro jobs API %s for %s %s", resp.status_code, self.branch_config["name"], company_name)
                        return {}
                    resp.raise_for_status()
                    items = resp.json()
                    if not isinstance(items, list):
                        items = items.get("items") or items.get("values") or []
                    if not items:
                        break
                    for job in items:
                        if self._is_stock_order(job):
                            continue
                        jobs_completed += 1
                    if len(items) < page_size:
                        break
                    page += 1
                    await asyncio.sleep(0.05)
                except httpx.HTTPError as e:
                    logger.error("Simpro jobs Complete fetch %s %s: %s", self.branch_config["name"], company_name, e)
                    return {}

            # ---- Pending + Progress: count total and how many have DueDate ----
            for stage in ("Pending", "Progress"):
                page = 1
                while True:
                    try:
                        resp = await client.get(
                            f"{self.api_url}/companies/{company_id}/jobs/",
                            params={
                                "columns": "ID,Name,Site,Stage,DueDate",
                                "pageSize": page_size,
                                "page": page,
                                "Stage": stage,
                            },
                            headers=headers,
                        )
                        if resp.status_code in (401, 404):
                            return {}
                        resp.raise_for_status()
                        items = resp.json()
                        if not isinstance(items, list):
                            items = items.get("items") or items.get("values") or []
                        if not items:
                            break
                        for job in items:
                            if self._is_stock_order(job):
                                continue
                            total_pending_jobs += 1
                            due = job.get("DueDate")
                            if due is not None and str(due).strip() != "":
                                pending_jobs_with_due_dates += 1
                        if len(items) < page_size:
                            break
                        page += 1
                        await asyncio.sleep(0.05)
                    except httpx.HTTPError as e:
                        logger.error("Simpro jobs %s fetch %s %s: %s", stage, self.branch_config["name"], company_name, e)
                        return {}

        result = {
            "jobs_completed": jobs_completed,
            "total_pending_jobs": total_pending_jobs,
            "pending_jobs_with_due_dates": pending_jobs_with_due_dates,
        }
        logger.info(
            "Simpro job stats %s %s: completed=%d pending=%d pending_with_due=%d",
            self.branch_config["name"], company_name, jobs_completed, total_pending_jobs, pending_jobs_with_due_dates,
        )
        return result

    @staticmethod
    def _parse_quote_date_issued(value) -> Optional[date]:
        if value is None or (isinstance(value, str) and not str(value).strip()):
            return None
        s = str(value).strip().replace("Z", "+00:00")
        try:
            dt = datetime.fromisoformat(s)
        except ValueError:
            return None
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt.astimezone(PERCH_TZ).date()

    @staticmethod
    def _quote_total_ex_tax(total_val) -> float:
        if total_val is None:
            return 0.0
        if isinstance(total_val, dict):
            ex = total_val.get("ExTax")
            if ex is None and isinstance(total_val.get("Total"), dict):
                ex = total_val["Total"].get("ExTax")
            try:
                return float(ex or 0)
            except (TypeError, ValueError):
                return 0.0
        try:
            return float(total_val)
        except (TypeError, ValueError):
            return 0.0

    @staticmethod
    def _is_quote_converted_status(status) -> bool:
        if status is None:
            return False
        if isinstance(status, dict):
            s = str(status.get("Name") or status.get("value") or "").strip().lower()
        else:
            s = str(status).strip().lower()
        if not s:
            return False
        return "converted to job" in s

    def _empty_quote_aggregates(self) -> dict:
        departments = []
        for company_id in sorted(self.companies.keys()):
            name = self.companies[company_id]["name"]
            departments.append({
                "name": name,
                "quotesCreated": 0,
                "totalValueQuoted": 0.0,
                "avgQuoteValue": 0.0,
                "quotesConverted": 0,
                "valueConverted": 0.0,
                "conversionRate": 0.0,
            })
        return {
            "quotesCreated": 0,
            "totalValueQuoted": 0.0,
            "avgQuoteValue": 0.0,
            "quotesConverted": 0,
            "valueConverted": 0.0,
            "conversionRate": 0.0,
            "departments": departments,
        }

    async def _fetch_quotes_for_company_range(
        self, company_id: int, date_from: date, date_to: date, client: httpx.AsyncClient, headers: dict
    ) -> Tuple[str, int, float, int, float]:
        """
        Paginate quotes (newest first) for one company; count quotes with DateIssued in [date_from, date_to] (Perth).
        Returns (department_name, created, total_value_ex, converted_count, converted_value_ex).
        """
        company_name = self.companies.get(company_id, {}).get("name", f"Company {company_id}")
        if not self.api_key:
            return company_name, 0, 0.0, 0, 0.0

        page_size = 250
        created = 0
        total_ex = 0.0
        converted_n = 0
        converted_ex = 0.0
        page = 1
        max_pages = 500

        while page <= max_pages:
            try:
                resp = await client.get(
                    f"{self.api_url}/companies/{company_id}/quotes/",
                    params={
                        "columns": "ID,Status,DateIssued,Total",
                        "pageSize": page_size,
                        "page": page,
                        "orderby": "-DateIssued",
                    },
                    headers=headers,
                )
                if resp.status_code in (401, 404):
                    logger.warning(
                        "Simpro quotes API %s for %s company %s",
                        resp.status_code,
                        self.branch_config["name"],
                        company_name,
                    )
                    return company_name, 0, 0.0, 0, 0.0
                resp.raise_for_status()
                data = resp.json()
                batch = data if isinstance(data, list) else data.get("items") or data.get("values") or []
                if not batch:
                    break

                oldest_in_batch: Optional[date] = None
                for row in batch:
                    di = self._parse_quote_date_issued(row.get("DateIssued"))
                    if di is not None and (oldest_in_batch is None or di < oldest_in_batch):
                        oldest_in_batch = di

                    if di is None:
                        continue
                    if di < date_from or di > date_to:
                        continue

                    qtot = self._quote_total_ex_tax(row.get("Total"))
                    created += 1
                    total_ex += qtot
                    if self._is_quote_converted_status(row.get("Status")):
                        converted_n += 1
                        converted_ex += qtot

                if oldest_in_batch is not None and oldest_in_batch < date_from:
                    break
                if len(batch) < page_size:
                    break
                page += 1
                await asyncio.sleep(0.05)
            except httpx.HTTPError as e:
                logger.error(
                    "Simpro quotes fetch %s %s page %s: %s",
                    self.branch_config["name"],
                    company_name,
                    page,
                    e,
                )
                return company_name, 0, 0.0, 0, 0.0

        return company_name, created, total_ex, converted_n, converted_ex

    async def fetch_quote_aggregates_for_branch(self, date_from: date, date_to: date) -> dict:
        """
        Quote KPIs for all companies in this branch, cohort = DateIssued in [date_from, date_to] (Australia/Perth).
        Converted = status name contains 'converted to job' (matches legacy CSV logic).
        """
        if not self.api_key:
            return self._empty_quote_aggregates()

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

        company_ids = sorted(self.companies.keys(), key=lambda x: int(x))
        async with httpx.AsyncClient(timeout=90.0) as client:
            tasks = [
                self._fetch_quotes_for_company_range(int(cid), date_from, date_to, client, headers)
                for cid in company_ids
            ]
            results = await asyncio.gather(*tasks, return_exceptions=True)

        per_company: Dict[int, Tuple[int, float, int, float]] = {}
        for idx, cid in enumerate(company_ids):
            res = results[idx] if idx < len(results) else None
            if isinstance(res, Exception):
                logger.warning("Simpro quote company fetch failed %s: %s", cid, res)
                per_company[cid] = (0, 0.0, 0, 0.0)
            else:
                _name, cr, tex, conv_n, conv_ex = res
                per_company[cid] = (cr, tex, conv_n, conv_ex)

        departments = []
        quotes_created = 0
        total_value_quoted = 0.0
        quotes_converted = 0
        value_converted = 0.0

        for company_id in company_ids:
            name = self.companies[company_id]["name"]
            cr, tex, conv_n, conv_ex = per_company.get(company_id, (0, 0.0, 0, 0.0))
            quotes_created += cr
            total_value_quoted += tex
            quotes_converted += conv_n
            value_converted += conv_ex
            avg_q = (tex / cr) if cr > 0 else 0.0
            conv_rate = (conv_n / cr * 100.0) if cr > 0 else 0.0
            departments.append({
                "name": name,
                "quotesCreated": cr,
                "totalValueQuoted": round(tex, 2),
                "avgQuoteValue": round(avg_q, 2),
                "quotesConverted": conv_n,
                "valueConverted": round(conv_ex, 2),
                "conversionRate": round(conv_rate, 1),
            })

        avg_quote_value = (total_value_quoted / quotes_created) if quotes_created > 0 else 0.0
        conversion_rate = (quotes_converted / quotes_created * 100.0) if quotes_created > 0 else 0.0

        out = {
            "quotesCreated": quotes_created,
            "totalValueQuoted": round(total_value_quoted, 2),
            "avgQuoteValue": round(avg_quote_value, 2),
            "quotesConverted": quotes_converted,
            "valueConverted": round(value_converted, 2),
            "conversionRate": round(conversion_rate, 1),
            "departments": departments,
        }
        logger.info(
            "Simpro quotes %s: %d issued in range, %d converted, $%.2f quoted",
            self.branch_config["name"],
            quotes_created,
            quotes_converted,
            total_value_quoted,
        )
        return out

    async def fetch_all_companies_invoices(
        self,
        year: int = 2025,
        since_date: Optional[datetime] = None
    ) -> List[Dict]:
        """
        Fetch invoices for all companies in this branch.
        
        Args:
            year: Year to fetch invoices for
            since_date: Only fetch invoices after this date
            
        Returns:
            List of all invoices across all companies
        """
        tasks = []
        for company_id in self.companies.keys():
            task = self.fetch_company_invoices(company_id, year, since_date)
            tasks.append(task)
        
        # Fetch all companies in parallel
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Combine results and filter out exceptions
        all_invoices = []
        for result in results:
            if isinstance(result, list):
                all_invoices.extend(result)
            elif isinstance(result, Exception):
                logger.error(f"Error fetching company invoices: {result}")
        
        return all_invoices

