import logging
import httpx
from datetime import datetime
from typing import List, Optional, Dict, Any
from pymongo import MongoClient
from bson import ObjectId
from fastapi import HTTPException

from src.config import mongo_settings, woodpecker_settings
from src.emailCampaigns.models import (
    MailboxResponse,
    MailboxDetails,
    MailboxMongo,
    CampaignResponse,
    CampaignMongo,
    CampaignCreateRequest,
    MailboxBulkRequest,
    Prospect,
    AddProspectsRequest,
    AddProspectsV1Request,
    CampaignIdWrapper,
    WoodpeckerSyncResponse,
    CampaignUpdateRequest
)

logger = logging.getLogger(__name__)

class WoodpeckerService:
    def __init__(self):
        self.mongo_client = MongoClient(mongo_settings.uri)
        self.db = self.mongo_client[mongo_settings.db]
        self.mailboxes_col = self.db["woodpecker_mailboxes"]
        self.campaigns_col = self.db["woodpecker_campaigns"]
        
        self.api_key = woodpecker_settings.api_key
        self.base_url = woodpecker_settings.base_url
        self.headers = {
            "x-api-key": self.api_key,
            "Content-Type": "application/json"
        }

    async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Any:
        async with httpx.AsyncClient() as client:
            url = f"{self.base_url}{endpoint}"
            response = await client.get(url, headers=self.headers, params=params)
            response.raise_for_status()
            return response.json()

    async def _post(self, endpoint: str, data: Any) -> Any:
        async with httpx.AsyncClient() as client:
            url = f"{self.base_url}{endpoint}"
            response = await client.post(url, headers=self.headers, json=data)
            response.raise_for_status()
            if response.status_code == 204 or not response.text.strip():
                return {"status": "success", "code": response.status_code}
            return response.json()

    async def _patch(self, endpoint: str, data: Any) -> Any:
        async with httpx.AsyncClient() as client:
            url = f"{self.base_url}{endpoint}"
            response = await client.patch(url, headers=self.headers, json=data)
            response.raise_for_status()
            return response.json()

    # ──────────────────────────────────────────────
    # Mailbox Methods
    # ──────────────────────────────────────────────

    async def list_mailboxes(self, user_id: str, force_sync: bool = False) -> List[MailboxMongo]:
        if force_sync:
            await self.resync_mailboxes(user_id)
        
        docs = list(self.mailboxes_col.find({"user_id": user_id}))
        return [MailboxMongo(**d) for d in docs]

    async def get_active_mailbox(self, user_id: str) -> Optional[MailboxMongo]:
        """Returns the first active mailbox for the user."""
        doc = self.mailboxes_col.find_one({"user_id": user_id, "status": "active"})
        if doc:
            return MailboxMongo(**doc)
        return None

    async def get_mailbox_summary(self, mailbox_id: int) -> MailboxDetails:
        """Get 24h summary for a mailbox directly from API."""
        data = await self._get(f"/v2/mailboxes/{mailbox_id}")
        # Assuming the response follows the structure mentioned by user
        details = data.get("details", {})
        return MailboxDetails(sent_today=details.get("sent_today", 0))

    async def add_mailboxes_bulk(self, user_id: str, request: MailboxBulkRequest) -> Dict:
        """Add mailboxes in bulk."""
        endpoint = "/v2/mailboxes/manual_connection/bulk"
        data = request.model_dump()
        result = await self._post(endpoint, data)
        # After adding, we should probably sync
        await self.resync_mailboxes(user_id)
        return result

    # ──────────────────────────────────────────────
    # Campaign Methods
    # ──────────────────────────────────────────────

    async def list_campaigns(self, user_id: str, force_sync: bool = False) -> List[CampaignMongo]:
        if force_sync:
            await self.resync_campaigns(user_id)
        
        docs = list(self.campaigns_col.find({"user_id": user_id}))
        return [CampaignMongo(**d) for d in docs]

    async def create_campaign(self, user_id: str, request: CampaignCreateRequest) -> CampaignResponse:
        endpoint = "/v2/campaigns"
        data = request.model_dump(exclude_none=True)
        
        # Inject cc and bcc from env if not provided
        if "cc" not in data and woodpecker_settings.cc:
            data["cc"] = woodpecker_settings.cc
        if "bcc" not in data and woodpecker_settings.bcc:
            data["bcc"] = woodpecker_settings.bcc
            
        result = await self._post(endpoint, data)

        # result should be the created campaign
        campaign = CampaignResponse(**result)
        # Sync to Mongo
        self.campaigns_col.update_one(
            {"id": campaign.id, "user_id": user_id},
            {"$set": CampaignMongo(user_id=user_id, **campaign.model_dump()).model_dump()},
            upsert=True
        )
        return campaign

    async def update_campaign(self, user_id: str, campaign_id: int, request: CampaignUpdateRequest) -> Dict:
        endpoint = f"/v2/campaigns/{campaign_id}"
        data = request.model_dump(exclude_none=True)
        return await self._patch(endpoint, data)

    async def add_prospects_to_campaign(self, request: AddProspectsRequest) -> Dict:
        """Add prospects using v1 API."""
        # Note: v1 API might have different base URL or auth. 
        # User specified: POST https://api.woodpecker.co/rest/v1/add_prospects_campaign
        endpoint = "/v1/add_prospects_campaign"
        data = request.model_dump()
        # v1 might use Woodpecker-specific headers or different body structure.
        # Assuming our helper works or we adjust if needed.
        async with httpx.AsyncClient() as client:
            url = f"{self.base_url}{endpoint}"
            # Woodpecker v1 often uses App-Token
            headers = self.headers.copy()
            response = await client.post(url, headers=headers, json=data)
            response.raise_for_status()
            return response.json()

    async def add_prospects_to_campaign_v1(self, request: AddProspectsV1Request) -> Dict:
        """Add prospects using v1 API with snippets support."""
        endpoint = "/v1/add_prospects_campaign"
        data = request.model_dump()
        
        async with httpx.AsyncClient() as client:
            url = f"{self.base_url}{endpoint}"
            response = await client.post(url, headers=self.headers, json=data)
            response.raise_for_status()
            return response.json()

    async def run_campaign(self, campaign_id: int) -> Dict:
        endpoint = f"/v2/campaigns/{campaign_id}/run"
        return await self._post(endpoint, {})

    # ──────────────────────────────────────────────
    # Intelligent Resync Logic
    # ──────────────────────────────────────────────

    async def resync_mailboxes(self, user_id: str) -> int:
        """Fetch all mailboxes from Woodpecker and update MongoDB."""
        try:
            data = await self._get("/v2/mailboxes")
            
            # List of mailboxes
            mailboxes = data if isinstance(data, list) else data.get("mailboxes", [])
            
            synced_ids = []
            for m in mailboxes:
                mailbox_resp = MailboxResponse(**m)
                mailbox_mongo = MailboxMongo(
                    user_id=user_id,
                    last_synced_at=datetime.now(),
                    **mailbox_resp.model_dump()
                )
                self.mailboxes_col.update_one(
                    {"id": mailbox_resp.id, "user_id": user_id},
                    {"$set": mailbox_mongo.model_dump()},
                    upsert=True
                )
                synced_ids.append(mailbox_resp.id)
            
            # Intelligent part: Mark mailboxes not in the response as "not_found"
            self.mailboxes_col.update_many(
                {"user_id": user_id, "id": {"$nin": synced_ids}},
                {"$set": {"status": "not_found", "last_synced_at": datetime.now()}}
            )
            
            return len(synced_ids)
        except Exception as e:
            logger.error(f"Error resyncing mailboxes: {e}")
            return 0

    async def resync_campaigns(self, user_id: str) -> int:
        """Fetch all campaigns from Woodpecker and update MongoDB."""
        try:
            data = await self._get("/v1/campaign_list")
            campaigns = data if isinstance(data, list) else data.get("campaigns", [])
            synced_ids = []
            for c in campaigns:
                campaign_resp = CampaignResponse(**c)
                campaign_mongo = CampaignMongo(
                    user_id=user_id,
                    last_synced_at=datetime.now(),
                    **campaign_resp.model_dump()
                )
                self.campaigns_col.update_one(
                    {"id": campaign_resp.id, "user_id": user_id},
                    {"$set": campaign_mongo.model_dump()},
                    upsert=True
                )
                synced_ids.append(campaign_resp.id)
            
            # Mark others as stopped or not_found
            self.campaigns_col.update_many(
                {"user_id": user_id, "id": {"$nin": synced_ids}},
                {"$set": {"status": "not_found", "last_synced_at": datetime.now()}}
            )
            
            return len(synced_ids)
        except Exception as e:
            logger.error(f"Error resyncing campaigns: {e}")
            return 0

    async def sync_all(self, user_id: str) -> WoodpeckerSyncResponse:
        m_count = await self.resync_mailboxes(user_id)
        c_count = await self.resync_campaigns(user_id)
        return WoodpeckerSyncResponse(
            success=True,
            mailboxes_synced=m_count,
            campaigns_synced=c_count,
            message=f"Synced {m_count} mailboxes and {c_count} campaigns."
        )
