"""Salesforce Developer skill — SFDX metadata stubs from approved design."""

from __future__ import annotations

import re
from typing import Dict, List, Optional
from uuid import UUID

from packages.artifacts.design import DesignArtifact, MetadataPlanItem, enforce_allowlist
from packages.artifacts.implementation import ImplementationArtifact
from packages.artifacts.requirements import RequirementsArtifact
from packages.artifacts.review import ReviewArtifact
from packages.db.artifacts import ArtifactStore
from packages.integrations.llm import LLMClient, get_llm_client

_STUB_TYPES = frozenset(
    {
        "CustomField",
        "ValidationRule",
        "PermissionSet",
        "ApexClass",
        "ApexTrigger",
        "Flow",
        "LightningComponentBundle",
    }
)


class SalesforceDeveloper:
    def __init__(
        self,
        artifacts: Optional[ArtifactStore] = None,
        llm: Optional[LLMClient] = None,
    ) -> None:
        self.artifacts = artifacts or ArtifactStore()
        self.llm = llm or get_llm_client()

    async def implement(
        self,
        design: DesignArtifact,
        requirements: Optional[RequirementsArtifact],
        run_id: UUID,
    ) -> tuple[ImplementationArtifact, Dict[str, str]]:
        cleaned = enforce_allowlist(design)
        if not cleaned.metadata_plan:
            raise ValueError("Design metadata plan empty after allowlist enforcement")

        files: Dict[str, str] = {}
        deferred: list[str] = []
        written: list[str] = []

        for item in cleaned.metadata_plan:
            if item.metadata_type in _STUB_TYPES:
                for rel_path, content in _render_stub(item):
                    self.artifacts.write_text(run_id, rel_path, content)
                    files[rel_path] = content
                    written.append(rel_path)
            else:
                key = f"{item.metadata_type}:{item.api_name}"
                if key not in deferred:
                    deferred.append(key)

        summary_lines = [
            f"# Implementation summary — {cleaned.jira_key}",
            "",
            f"**Design:** {cleaned.summary}",
            "",
            "## Files written",
        ]
        if written:
            for p in written:
                summary_lines.append(f"- `{p}`")
        else:
            summary_lines.append("- (none)")
        if deferred:
            summary_lines.extend(["", "## Deferred (unsupported metadata type)", ""])
            for d in deferred:
                summary_lines.append(f"- {d}")
        if requirements and requirements.acceptance_criteria:
            summary_lines.extend(["", "## Acceptance criteria", ""])
            for ac in requirements.acceptance_criteria:
                summary_lines.append(f"- {ac}")

        summary_md = "\n".join(summary_lines)
        self.artifacts.write_text(run_id, "implementation-summary.md", summary_md)
        written.append("implementation-summary.md")
        files["implementation-summary.md"] = summary_md

        commit_message = f"Agent Nova: {cleaned.jira_key} — {cleaned.summary[:80]}"
        artifact = ImplementationArtifact(
            jira_key=cleaned.jira_key,
            commit_message=commit_message,
            files_written=written,
            summary=f"Generated {len([w for w in written if w.startswith('force-app/')])} metadata stub file(s) for {cleaned.jira_key}",
            deferred_types=deferred,
            notes="Phase 3b — stubs + lower-org deploy validate before draft PR",
        )
        return artifact, files

    async def apply_fixes(
        self,
        *,
        review: ReviewArtifact,
        design: DesignArtifact,
        requirements: Optional[RequirementsArtifact],
        run_id: UUID,
        file_map: Dict[str, str],
    ) -> tuple[ImplementationArtifact, Dict[str, str]]:
        fixable = [f for f in review.findings if f.fixable]
        if not fixable:
            raise ValueError("No fixable review findings to apply")

        force_app_files = {
            p: c for p, c in file_map.items() if p.startswith("force-app/")
        }
        prompt = build_fix_prompt(
            review=review,
            design=design,
            requirements=requirements,
            file_map=force_app_files,
        )
        patches = await self.llm.fix_code(prompt)

        updated_files = dict(file_map)
        written: list[str] = []
        for path, content in patches.items():
            if not path.startswith("force-app/"):
                continue
            self.artifacts.write_text(run_id, path, content)
            updated_files[path] = content
            if path not in written:
                written.append(path)

        impl_raw = self.artifacts.read_json(run_id, "implementation.json")
        prior = ImplementationArtifact.model_validate(impl_raw)
        all_written = list(dict.fromkeys(prior.files_written + written))
        commit_message = prior.commit_message
        artifact = ImplementationArtifact(
            jira_key=design.jira_key,
            commit_message=commit_message,
            files_written=all_written,
            summary=f"Applied review fixes (cycle {review.cycle}): {len(written)} file(s) updated",
            deferred_types=prior.deferred_types,
            notes=f"Phase 4 fix cycle {review.cycle}",
        )
        self.artifacts.write_json(run_id, "implementation.json", artifact)
        return artifact, updated_files


def build_fix_prompt(
    *,
    review: ReviewArtifact,
    design: DesignArtifact,
    requirements: Optional[RequirementsArtifact],
    file_map: Dict[str, str],
) -> str:
    findings = "\n".join(
        f"- [{f.severity}/{f.category}] {f.file or 'general'}: {f.message}"
        for f in review.findings
        if f.fixable
    ) or "(none)"
    ac = "\n".join(f"- {a}" for a in (requirements.acceptance_criteria if requirements else [])) or "(none)"
    files_section = _format_fix_files(file_map)

    return f"""You are the Salesforce Developer fixing code review findings for Agent Nova.
Apply minimal fixes only. Do not add metadata outside the approved design plan.

Jira key: {design.jira_key}
Design summary: {design.summary}

Acceptance criteria:
{ac}

Findings to address:
{findings}

Metadata plan (do not exceed):
{chr(10).join(f"- {i.metadata_type} {i.api_name}" for i in design.metadata_plan)}

Files to fix:
{files_section}

Return updated file paths and full file contents under force-app/ only.
"""


def _format_fix_files(file_map: Dict[str, str], max_chars: int = 4000) -> str:
    if not file_map:
        return "(none)"
    parts: list[str] = []
    for path, content in sorted(file_map.items()):
        snippet = content if len(content) <= max_chars else content[:max_chars] + "\n... truncated"
        parts.append(f"### {path}\n```\n{snippet}\n```")
    return "\n\n".join(parts)


def _render_stub(item: MetadataPlanItem) -> List[tuple[str, str]]:
    obj = item.object or "Account"
    if item.metadata_type == "CustomField":
        path = f"force-app/main/default/objects/{obj}/fields/{item.api_name}.field-meta.xml"
        content = f"""<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{item.api_name}</fullName>
    <label>{item.api_name.replace('__c', '').replace('_', ' ')}</label>
    <type>Picklist</type>
    <valueSet>
        <valueSetDefinition>
            <sorted>false</sorted>
            <value><fullName>Bronze</fullName><default>false</default></value>
            <value><fullName>Silver</fullName><default>false</default></value>
            <value><fullName>Gold</fullName><default>false</default></value>
        </valueSetDefinition>
    </valueSet>
    <description>{item.notes or 'Agent Nova stub'}</description>
</CustomField>
"""
        return [(path, content)]

    if item.metadata_type == "ValidationRule":
        path = f"force-app/main/default/objects/{obj}/validationRules/{item.api_name}.validationRule-meta.xml"
        content = f"""<?xml version="1.0" encoding="UTF-8"?>
<ValidationRule xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{item.api_name}</fullName>
    <active>true</active>
    <errorConditionFormula>AND(
        ISPICKVAL(Customer_Tier__c, "Gold"),
        AnnualRevenue &lt; 100000
    )</errorConditionFormula>
    <errorMessage>Gold tier requires Annual Revenue of at least 100,000.</errorMessage>
    <description>{item.notes or 'Agent Nova stub'}</description>
</ValidationRule>
"""
        return [(path, content)]

    if item.metadata_type == "PermissionSet":
        path = f"force-app/main/default/permissionsets/{item.api_name}.permissionset-meta.xml"
        content = f"""<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>{item.api_name.replace('_', ' ')}</label>
    <description>{item.notes or 'Agent Nova stub'}</description>
    <hasActivationRequired>false</hasActivationRequired>
</PermissionSet>
"""
        return [(path, content)]

    if item.metadata_type == "ApexClass":
        name = item.api_name
        cls_path = f"force-app/main/default/classes/{name}.cls"
        meta_path = f"force-app/main/default/classes/{name}.cls-meta.xml"
        cls_body = f"""public without sharing class {name} {{
    public static void run() {{
        // Agent Nova stub — implement business logic
    }}
}}
"""
        meta_body = f"""<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>59.0</apiVersion>
    <status>Active</status>
</ApexClass>
"""
        return [(cls_path, cls_body), (meta_path, meta_body)]

    if item.metadata_type == "ApexTrigger":
        name = item.api_name
        trig_path = f"force-app/main/default/triggers/{name}.trigger"
        meta_path = f"force-app/main/default/triggers/{name}.trigger-meta.xml"
        trig_body = f"""trigger {name} on {obj} (before insert, before update) {{
    // Agent Nova stub — bulk-safe handler recommended
}}
"""
        meta_body = f"""<?xml version="1.0" encoding="UTF-8"?>
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>59.0</apiVersion>
    <status>Active</status>
</ApexTrigger>
"""
        return [(trig_path, trig_body), (meta_path, meta_body)]

    if item.metadata_type == "Flow":
        name = item.api_name
        path = f"force-app/main/default/flows/{name}.flow-meta.xml"
        content = f"""<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>59.0</apiVersion>
    <description>{item.notes or 'Agent Nova record-triggered Flow stub'}</description>
    <interviewLabel>{name} {{!$Flow.CurrentDateTime}}</interviewLabel>
    <label>{name.replace('_', ' ')}</label>
    <processMetadataValues>
        <name>BuilderType</name>
        <value>
            <stringValue>LightningFlowBuilder</stringValue>
        </value>
    </processMetadataValues>
    <processType>AutoLaunchedFlow</processType>
    <start>
        <locationX>50</locationX>
        <locationY>0</locationY>
        <connector>
            <targetReference>Stub_Assignment</targetReference>
        </connector>
        <object>{obj}</object>
        <recordTriggerType>CreateAndUpdate</recordTriggerType>
        <triggerType>RecordAfterSave</triggerType>
    </start>
    <status>Draft</status>
    <assignments>
        <name>Stub_Assignment</name>
        <label>Stub Assignment</label>
        <locationX>176</locationX>
        <locationY>134</locationY>
        <assignmentItems>
            <assignToReference>$Flow.CurrentDateTime</assignToReference>
            <operator>Assign</operator>
            <value>
                <elementReference>$Flow.CurrentDateTime</elementReference>
            </value>
        </assignmentItems>
    </assignments>
</Flow>
"""
        return [(path, content)]

    if item.metadata_type == "LightningComponentBundle":
        camel = _to_lwc_folder_name(item.api_name)
        base = f"force-app/main/default/lwc/{camel}"
        html = f"""<template>
    <div class="slds-m-around_medium">
        <p>{item.api_name} (Agent Nova LWC stub)</p>
    </div>
</template>
"""
        js = f"""import {{ LightningElement }} from 'lwc';

export default class {_to_js_class_name(camel)} extends LightningElement {{}}
"""
        meta = f"""<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>59.0</apiVersion>
    <isExposed>false</isExposed>
    <description>{item.notes or 'Agent Nova LWC stub'}</description>
</LightningComponentBundle>
"""
        return [
            (f"{base}/{camel}.html", html),
            (f"{base}/{camel}.js", js),
            (f"{base}/{camel}.js-meta.xml", meta),
        ]

    raise ValueError(f"Unsupported stub type: {item.metadata_type}")


def _to_lwc_folder_name(api_name: str) -> str:
    """LWC folder names are camelCase starting with a lowercase letter."""
    name = re.sub(r"[^A-Za-z0-9_]", "", api_name or "agentNovaStub")
    if not name:
        name = "agentNovaStub"
    # PascalCase / snake → camelCase
    if "_" in name:
        parts = [p for p in name.split("_") if p]
        name = parts[0].lower() + "".join(p[:1].upper() + p[1:] for p in parts[1:])
    elif name[0].isupper():
        name = name[0].lower() + name[1:]
    if not name[0].islower():
        name = "c" + name
    return name


def _to_js_class_name(folder: str) -> str:
    return folder[:1].upper() + folder[1:] if folder else "AgentNovaStub"
