"""
API endpoints for dimensional accounting code management.
"""

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field
from datetime import date

from app.core.database import get_db
from app.services.dimensional_code_service import DimensionalCodeService
from app.models.accounting import AccountingCode, AccountingCodeInstance


router = APIRouter(prefix="/api/v1/dimensional-codes", tags=["Dimensional Codes"])


# Pydantic models for request/response
class DimensionValuesSchema(BaseModel):
    """Dimension value assignments for instance generation"""
    values: Dict[str, Any] = Field(..., description="Dimension name to value mapping")


class GenerateInstanceRequest(BaseModel):
    """Request to generate a single dimensional code instance"""
    base_code_id: str = Field(..., description="Base accounting code ID")
    dimension_values: Dict[str, Any] = Field(..., description="Dimension assignments")
    template_id: Optional[str] = Field(None, description="Optional template ID (defaults to base_code_id)")


class GenerateBatchRequest(BaseModel):
    """Request to generate multiple instances"""
    base_code_id: str = Field(..., description="Base accounting code ID")
    dimension_value_sets: List[Dict[str, Any]] = Field(..., description="List of dimension value dicts")
    template_id: Optional[str] = Field(None, description="Optional template ID")


class PreviewInstanceRequest(BaseModel):
    """Request to preview an instance without creating it"""
    base_code_id: str = Field(..., description="Base accounting code ID")
    dimension_values: Dict[str, Any] = Field(..., description="Dimension assignments")
    template_id: Optional[str] = Field(None, description="Optional template ID")


class RegenerateTemplateRequest(BaseModel):
    """Request to regenerate all instances for a template"""
    template_id: str = Field(..., description="Template code ID")
    dimension_value_sets: List[Dict[str, Any]] = Field(..., description="List of dimension combinations")
    clear_existing: bool = Field(False, description="Delete existing instances first")


class InstanceResponse(BaseModel):
    """Response schema for dimensional code instance"""
    id: str
    base_code_id: str
    created_from_template_id: Optional[str]
    instance_code: str
    name: str
    account_type: str
    category: str
    reporting_tag: Optional[str]
    branch_id: Optional[str]
    dimension_values: Optional[Dict[str, Any]]

    class Config:
        from_attributes = True


class TemplateResponse(BaseModel):
    """Response schema for dimensional template"""
    id: str
    code: str
    name: str
    account_type: str
    category: str
    is_dimensional_template: bool
    dimension_pattern: Optional[Dict[str, Any]]
    parent_id: Optional[str]

    class Config:
        from_attributes = True


class DimensionalBalanceResponse(BaseModel):
    """Response schema for dimensional balances"""
    dimension_type: str
    dimension_value_id: str
    dimension_value_code: str
    dimension_value_name: str
    total_debits: float
    total_credits: float
    net_balance: float


@router.get("/templates", response_model=List[TemplateResponse])
def list_templates(
    include_inactive: bool = Query(False, description="Include inactive templates"),
    db: Session = Depends(get_db)
):
    """
    Get all dimensional code templates.

    Templates are accounting codes marked with is_dimensional_template=True
    and have a dimension_pattern defined.
    """
    service = DimensionalCodeService(db)
    templates = service.get_template_list(include_inactive=include_inactive)
    return templates


@router.post("/generate", response_model=InstanceResponse)
def generate_instance(
    request: GenerateInstanceRequest,
    db: Session = Depends(get_db)
):
    """
    Generate a single dimensional code instance from a template.

    Example:
    ```json
    {
        "base_code_id": "uuid-of-base-code",
        "dimension_values": {
            "branch": "HQ",
            "branch_code": "HQ",
            "branch_name": "Headquarters",
            "department": "IT",
            "department_code": "IT",
            "department_name": "IT Department"
        }
    }
    ```
    """
    service = DimensionalCodeService(db)

    try:
        instance = service.generate_instance(
            base_code_id=request.base_code_id,
            dimension_values=request.dimension_values,
            template_id=request.template_id
        )
        return instance
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to generate instance: {str(e)}")


@router.post("/generate-batch", response_model=List[InstanceResponse])
def generate_instances_batch(
    request: GenerateBatchRequest,
    db: Session = Depends(get_db)
):
    """
    Generate multiple dimensional code instances in a single transaction.

    Example:
    ```json
    {
        "base_code_id": "uuid-of-base-code",
        "dimension_value_sets": [
            {
                "branch": "HQ",
                "department": "IT"
            },
            {
                "branch": "HQ",
                "department": "Sales"
            }
        ]
    }
    ```
    """
    service = DimensionalCodeService(db)

    try:
        instances = service.generate_instances_batch(
            base_code_id=request.base_code_id,
            dimension_value_sets=request.dimension_value_sets,
            template_id=request.template_id
        )
        return instances
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to generate instances: {str(e)}")


@router.post("/preview", response_model=Dict[str, Any])
def preview_instance(
    request: PreviewInstanceRequest,
    db: Session = Depends(get_db)
):
    """
    Preview what a dimensional code instance would look like without creating it.

    Useful for validating patterns and dimension values before actual generation.
    """
    service = DimensionalCodeService(db)

    try:
        preview = service.preview_instance(
            base_code_id=request.base_code_id,
            dimension_values=request.dimension_values,
            template_id=request.template_id
        )
        return preview
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to preview instance: {str(e)}")


@router.get("/instances/{base_code_id}", response_model=List[InstanceResponse])
def get_instances_for_base_code(
    base_code_id: str,
    dimension_filters: Optional[str] = Query(None, description="JSON string for dimension filtering"),
    db: Session = Depends(get_db)
):
    """
    Get all instances generated from a base code.

    Optionally filter by dimension values using a JSON filter string.
    """
    service = DimensionalCodeService(db)

    # Parse dimension_filters if provided
    filters = None
    if dimension_filters:
        import json
        try:
            filters = json.loads(dimension_filters)
        except json.JSONDecodeError:
            raise HTTPException(status_code=400, detail="Invalid JSON in dimension_filters")

    instances = service.get_instances_for_base_code(
        base_code_id=base_code_id,
        dimension_filters=filters
    )
    return instances


@router.delete("/instances/{instance_id}")
def delete_instance(
    instance_id: str,
    db: Session = Depends(get_db)
):
    """
    Delete a dimensional code instance.
    """
    service = DimensionalCodeService(db)

    deleted = service.delete_instance(instance_id)
    if not deleted:
        raise HTTPException(status_code=404, detail="Instance not found")

    return {"success": True, "message": "Instance deleted"}


@router.post("/regenerate", response_model=List[InstanceResponse])
def regenerate_template_instances(
    request: RegenerateTemplateRequest,
    db: Session = Depends(get_db)
):
    """
    Regenerate all instances for a template.

    Optionally clears existing instances first (clear_existing=true).
    """
    service = DimensionalCodeService(db)

    try:
        instances = service.regenerate_instances_for_template(
            template_id=request.template_id,
            dimension_value_sets=request.dimension_value_sets,
            clear_existing=request.clear_existing
        )
        return instances
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to regenerate instances: {str(e)}")


@router.get("/combinations")
def get_dimension_combinations(
    dimension_names: List[str] = Query(..., description="List of dimension names to combine"),
    db: Session = Depends(get_db)
):
    """
    Generate all possible combinations of dimension values.

    Example: ?dimension_names=branch&dimension_names=department

    Returns all combinations of active dimension values.
    """
    service = DimensionalCodeService(db)

    try:
        combinations = service.get_dimension_combinations(dimension_names)
        return {
            "dimension_names": dimension_names,
            "combinations": combinations,
            "count": len(combinations)
        }
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to get combinations: {str(e)}")


@router.get("/balances/{base_code_id}", response_model=List[DimensionalBalanceResponse])
def get_dimensional_balances(
    base_code_id: str,
    as_of_date: Optional[str] = Query(None, description="As-of date (YYYY-MM-DD)"),
    dimension_types: Optional[List[str]] = Query(None, description="Filter by dimension types"),
    db: Session = Depends(get_db)
):
    """
    View balances grouped by dimension values for a base accounting code.
    """
    service = DimensionalCodeService(db)

    parsed_date = None
    if as_of_date:
        try:
            parsed_date = date.fromisoformat(as_of_date)
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid as_of_date format; expected YYYY-MM-DD")

    try:
        balances = service.get_dimensional_balances(
            base_code_id=base_code_id,
            as_of_date=parsed_date,
            dimension_types=dimension_types
        )
        return balances
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to fetch dimensional balances: {str(e)}")


@router.post("/validate-template/{template_id}")
def validate_template(
    template_id: str,
    db: Session = Depends(get_db)
):
    """
    Validate that an accounting code is properly configured as a dimensional template.

    Returns validation status and any error messages.
    """
    service = DimensionalCodeService(db)

    template = db.query(AccountingCode).filter_by(id=template_id).first()
    if not template:
        raise HTTPException(status_code=404, detail="Template not found")

    is_valid, error = service.validate_template(template)

    return {
        "is_valid": is_valid,
        "error": error,
        "template_id": template_id,
        "template_code": template.code,
        "template_name": template.name
    }


class TransactionDetailResponse(BaseModel):
    """Response schema for transaction details"""
    journal_entry_id: str
    accounting_entry_id: Optional[str]
    date: date
    reference: Optional[str]
    description: Optional[str]
    particulars: Optional[str]
    debit_amount: float
    credit_amount: float
    dimension_assignments: List[Dict[str, Any]]
    account_code: str
    account_name: str


@router.get("/transactions/{base_code_id}", response_model=List[TransactionDetailResponse])
def get_dimensional_transactions(
    base_code_id: str,
    dimension_value_id: Optional[str] = Query(None, description="Filter by specific dimension value"),
    dimension_type: Optional[str] = Query(None, description="Filter by dimension type"),
    start_date: Optional[str] = Query(None, description="Start date (YYYY-MM-DD)"),
    end_date: Optional[str] = Query(None, description="End date (YYYY-MM-DD)"),
    limit: int = Query(100, ge=1, le=1000),
    offset: int = Query(0, ge=0),
    db: Session = Depends(get_db)
):
    """
    Get detailed transactions for a dimensional accounting code.

    This endpoint allows you to drill down into the actual journal entries
    that make up the dimensional balances.
    """
    service = DimensionalCodeService(db)

    parsed_start = None
    parsed_end = None

    if start_date:
        try:
            parsed_start = date.fromisoformat(start_date)
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid start_date format; expected YYYY-MM-DD")

    if end_date:
        try:
            parsed_end = date.fromisoformat(end_date)
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid end_date format; expected YYYY-MM-DD")

    try:
        transactions = service.get_dimensional_transactions(
            base_code_id=base_code_id,
            dimension_value_id=dimension_value_id,
            dimension_type=dimension_type,
            start_date=parsed_start,
            end_date=parsed_end,
            limit=limit,
            offset=offset
        )
        return transactions
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to fetch transactions: {str(e)}")
