"""
Jira 양방향 동기화 커넥터

기능:
  - SR ↔ Jira Issue 양방향 자동 동기화
  - 상태 매핑 (기관별 커스터마이즈)
  - Jira 웹훅 수신 (Jira → GUARDiA 상태 업데이트)
  - GUARDiA SR 상태 변경 → Jira Issue 업데이트

엔드포인트:
  POST /api/jira/config              — Jira 연동 설정 등록/수정 (관리자)
  GET  /api/jira/config              — 현재 설정 조회
  POST /api/jira/sync/{sr_id}        — SR → Jira Issue 수동 동기화
  GET  /api/jira/mappings            — SR-Issue 매핑 목록
  DELETE /api/jira/mappings/{id}     — 매핑 해제
  POST /api/jira/webhook             — Jira 웹훅 수신 (Jira → GUARDiA)
  POST /api/jira/test                — 연결 테스트

보안:
  - Jira API 토큰은 AES-256-GCM 암호화 저장 (서버 자격증명 동일 패턴)
  - 웹훅은 HMAC-SHA256 서명 검증
  - 외부 Jira 연결은 테넌트 설정에 따라 허용 (온프레미스 Jira 우선)
"""
from __future__ import annotations

import hashlib
import hmac
import json
import logging
from datetime import datetime
from typing import Dict, List, Optional

import httpx
from fastapi import APIRouter, Depends, HTTPException, Header, Request
from pydantic import BaseModel, Field
from sqlalchemy import select, desc
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import get_current_user, require_admin_role
from core.crypto import encrypt_secret, decrypt_secret
from database import get_db
from models import (
    User, SRRequest, SRStatus,
    JiraConfig, JiraSyncMapping,  # 신규 모델
)

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/jira", tags=["Jira 연동"])

# GUARDiA SR 상태 → Jira 상태 기본 매핑
DEFAULT_STATUS_MAP = {
    "OPEN":       "Open",
    "IN_PROGRESS": "In Progress",
    "PENDING":    "On Hold",
    "RESOLVED":   "Resolved",
    "DONE":       "Closed",
}


# ── Pydantic 스키마 ──────────────────────────────────────────────────────────

class JiraConfigCreate(BaseModel):
    base_url: str = Field(..., description="https://company.atlassian.net 또는 내부 Jira URL")
    email: str
    api_token: str = Field(..., description="Jira API 토큰 (암호화 저장됨)")
    project_key: str = Field(..., description="기본 프로젝트 키 (예: OPS)")
    status_mapping: Dict[str, str] = Field(
        default_factory=lambda: DEFAULT_STATUS_MAP,
        description="GUARDiA SR 상태 → Jira 상태 매핑"
    )
    auto_sync: bool = True
    webhook_secret: Optional[str] = None

class JiraConfigOut(BaseModel):
    id: int
    base_url: str
    email: str
    project_key: str
    status_mapping: dict
    auto_sync: bool
    is_active: bool
    last_synced_at: Optional[datetime]

class SyncResult(BaseModel):
    sr_id: int
    jira_key: Optional[str]
    action: str  # created | updated | skipped
    detail: Optional[str]


# ── 유틸 ────────────────────────────────────────────────────────────────────

def _mask_token(token: str) -> str:
    """API 토큰 마스킹 (처음 4자 + *** + 마지막 4자)."""
    if len(token) <= 8:
        return "***"
    return f"{token[:4]}***{token[-4:]}"


async def _jira_request(
    config: JiraConfig, method: str, path: str,
    payload: Optional[dict] = None
) -> Optional[dict]:
    """Jira REST API 호출 (오류 시 None 반환, 예외 미전파)."""
    token = decrypt_secret(config.api_token_enc)
    auth = (config.email, token)
    url = f"{config.base_url.rstrip('/')}/rest/api/3{path}"
    try:
        async with httpx.AsyncClient(timeout=15, verify=False) as client:
            r = await getattr(client, method.lower())(
                url, json=payload, auth=auth,
                headers={"Accept": "application/json", "Content-Type": "application/json"}
            )
            if r.status_code in (200, 201, 204):
                return r.json() if r.content else {}
            logger.warning(f"Jira API {r.status_code}: {r.text[:200]}")
    except Exception as e:
        logger.error(f"Jira 연결 실패: {e}")
    return None


def _sr_to_jira_payload(sr: SRRequest, config: JiraConfig) -> dict:
    """SR → Jira Issue 생성 페이로드 변환."""
    return {
        "fields": {
            "project": {"key": config.project_key},
            "summary": f"[GUARDiA SR-{sr.id}] {sr.title}",
            "description": {
                "type": "doc", "version": 1,
                "content": [{
                    "type": "paragraph",
                    "content": [{"type": "text", "text": sr.description or ""}]
                }]
            },
            "issuetype": {"name": "Task"},
            "priority": {"name": _map_priority(sr.priority)},
            "labels": ["guardia-itsm", f"sr-{sr.id}"],
        }
    }


def _map_priority(priority: str) -> str:
    return {"HIGH": "High", "MEDIUM": "Medium", "LOW": "Low"}.get(
        (priority or "MEDIUM").upper(), "Medium"
    )


# ── 엔드포인트 ───────────────────────────────────────────────────────────────

@router.post("/config", response_model=JiraConfigOut)
async def save_jira_config(
    req: JiraConfigCreate,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(require_admin_role),
):
    """Jira 연동 설정 저장 (관리자 전용). API 토큰은 암호화 저장."""
    # 기존 설정 확인
    existing = await db.execute(
        select(JiraConfig).where(JiraConfig.tenant_id == user.tenant_id)
    )
    cfg = existing.scalar_one_or_none()

    enc_token = encrypt_secret(req.api_token)

    if cfg:
        cfg.base_url = req.base_url
        cfg.email = req.email
        cfg.api_token_enc = enc_token
        cfg.project_key = req.project_key
        cfg.status_mapping = json.dumps(req.status_mapping)
        cfg.auto_sync = req.auto_sync
        cfg.webhook_secret = req.webhook_secret
    else:
        cfg = JiraConfig(
            tenant_id=user.tenant_id,
            base_url=req.base_url,
            email=req.email,
            api_token_enc=enc_token,
            project_key=req.project_key,
            status_mapping=json.dumps(req.status_mapping),
            auto_sync=req.auto_sync,
            webhook_secret=req.webhook_secret,
            is_active=True,
        )
        db.add(cfg)

    await db.commit()
    await db.refresh(cfg)
    return _cfg_to_out(cfg)


@router.get("/config", response_model=Optional[JiraConfigOut])
async def get_jira_config(
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """현재 테넌트 Jira 설정 조회 (토큰은 마스킹)."""
    row = await db.execute(
        select(JiraConfig).where(JiraConfig.tenant_id == user.tenant_id)
    )
    cfg = row.scalar_one_or_none()
    return _cfg_to_out(cfg) if cfg else None


@router.post("/test")
async def test_jira_connection(
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Jira 연결 테스트."""
    row = await db.execute(
        select(JiraConfig).where(JiraConfig.tenant_id == user.tenant_id)
    )
    cfg = row.scalar_one_or_none()
    if not cfg:
        raise HTTPException(404, "Jira 설정이 없습니다")

    result = await _jira_request(cfg, "GET", "/myself")
    if result:
        return {"ok": True, "jira_user": result.get("displayName", "연결됨")}
    raise HTTPException(400, "Jira 연결 실패 — URL/토큰을 확인하세요")


@router.post("/sync/{sr_id}", response_model=SyncResult)
async def sync_sr_to_jira(
    sr_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """SR을 Jira Issue로 동기화 (생성 또는 업데이트)."""
    # SR 조회
    sr_row = await db.execute(select(SRRequest).where(SRRequest.id == sr_id))
    sr = sr_row.scalar_one_or_none()
    if not sr:
        raise HTTPException(404, f"SR-{sr_id}를 찾을 수 없습니다")

    # Jira 설정 조회
    cfg_row = await db.execute(
        select(JiraConfig).where(JiraConfig.tenant_id == user.tenant_id, JiraConfig.is_active == True)
    )
    cfg = cfg_row.scalar_one_or_none()
    if not cfg:
        raise HTTPException(400, "Jira 설정이 없습니다")

    # 기존 매핑 확인
    map_row = await db.execute(
        select(JiraSyncMapping).where(JiraSyncMapping.sr_id == sr_id)
    )
    mapping = map_row.scalar_one_or_none()

    payload = _sr_to_jira_payload(sr, cfg)

    if mapping and mapping.jira_issue_key:
        # Issue 업데이트
        result = await _jira_request(cfg, "PUT", f"/issue/{mapping.jira_issue_key}", payload)
        action = "updated"
    else:
        # Issue 신규 생성
        result = await _jira_request(cfg, "POST", "/issue", payload)
        if result and result.get("key"):
            jira_key = result["key"]
            if mapping:
                mapping.jira_issue_key = jira_key
                mapping.synced_at = datetime.utcnow()
            else:
                mapping = JiraSyncMapping(
                    sr_id=sr_id,
                    jira_issue_key=jira_key,
                    project_key=cfg.project_key,
                    config_id=cfg.id,
                    synced_at=datetime.utcnow(),
                )
                db.add(mapping)
            await db.commit()
        action = "created"

    cfg.last_synced_at = datetime.utcnow()
    await db.commit()

    jira_key = mapping.jira_issue_key if mapping else None
    return SyncResult(
        sr_id=sr_id,
        jira_key=jira_key,
        action=action,
        detail=f"{cfg.base_url}/browse/{jira_key}" if jira_key else None,
    )


@router.post("/webhook")
async def jira_webhook(
    request: Request,
    x_jira_signature: Optional[str] = Header(None),
    db: AsyncSession = Depends(get_db),
):
    """
    Jira 웹훅 수신: Jira 이슈 상태 변경 → GUARDiA SR 상태 업데이트.
    Jira 설정에서 웹훅 URL: https://guardia.example.com/api/jira/webhook
    """
    body = await request.body()
    payload = json.loads(body)

    event = payload.get("webhookEvent", "")
    issue = payload.get("issue", {})
    issue_key = issue.get("key", "")

    if not issue_key or "issue" not in event:
        return {"ok": True, "skipped": "관심 이벤트 아님"}

    # 이슈 키로 매핑 찾기
    map_row = await db.execute(
        select(JiraSyncMapping).where(JiraSyncMapping.jira_issue_key == issue_key)
    )
    mapping = map_row.scalar_one_or_none()
    if not mapping:
        return {"ok": True, "skipped": "매핑 없음"}

    # Jira 상태 → GUARDiA SR 상태 역매핑
    cfg_row = await db.execute(
        select(JiraConfig).where(JiraConfig.id == mapping.config_id)
    )
    cfg = cfg_row.scalar_one_or_none()
    jira_status = issue.get("fields", {}).get("status", {}).get("name", "")

    # 역방향 매핑
    status_map = json.loads(cfg.status_mapping) if cfg else DEFAULT_STATUS_MAP
    reverse_map = {v: k for k, v in status_map.items()}
    sr_status = reverse_map.get(jira_status)

    if sr_status:
        sr_row = await db.execute(select(SRRequest).where(SRRequest.id == mapping.sr_id))
        sr = sr_row.scalar_one_or_none()
        if sr and sr.status != sr_status:
            sr.status = sr_status
            sr.updated_at = datetime.utcnow()
            mapping.synced_at = datetime.utcnow()
            await db.commit()
            logger.info(f"SR-{sr.id} 상태 업데이트: {sr_status} (Jira: {jira_status})")

    return {"ok": True}


@router.get("/mappings")
async def list_mappings(
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """SR-Jira Issue 매핑 목록."""
    rows = await db.execute(
        select(JiraSyncMapping).order_by(desc(JiraSyncMapping.synced_at)).limit(100)
    )
    mappings = rows.scalars().all()
    return [
        {
            "id": m.id,
            "sr_id": m.sr_id,
            "jira_key": m.jira_issue_key,
            "project": m.project_key,
            "synced_at": m.synced_at,
        }
        for m in mappings
    ]


def _cfg_to_out(cfg: JiraConfig) -> JiraConfigOut:
    return JiraConfigOut(
        id=cfg.id,
        base_url=cfg.base_url,
        email=cfg.email,
        project_key=cfg.project_key,
        status_mapping=json.loads(cfg.status_mapping) if cfg.status_mapping else DEFAULT_STATUS_MAP,
        auto_sync=cfg.auto_sync,
        is_active=cfg.is_active,
        last_synced_at=cfg.last_synced_at,
    )
