"""Flow-Based Test Suite Generator for socialseed-e2e.

This module generates complete test suites based on business flows detected
by the BusinessLogicInferenceEngine. It creates:
- Page classes with methods for each endpoint
- Data schemas with DTOs
- Test modules with flow-based tests
- Validation and chaos tests
"""

import re
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

from jinja2 import Template

from socialseed_e2e.project_manifest.business_logic_inference import (
    BusinessFlow,
    BusinessLogicInferenceEngine,
    FlowStep,
    FlowType,
)
from socialseed_e2e.project_manifest.db_model_parsers import DatabaseSchema
from socialseed_e2e.project_manifest.dummy_data_generator import (
    DataGenerationStrategy,
    DummyDataGenerator,
)
from socialseed_e2e.project_manifest.models import DtoSchema, EndpointInfo, ServiceInfo

# Template for generating data_schema.py
DATA_SCHEMA_TEMPLATE = '''"""Data schema for {{ service_name }} API.

Auto-generated by socialseed-e2e Flow-Based Test Generator.
Generated at: {{ generation_timestamp }}
"""
from pydantic import BaseModel, Field, EmailStr
from typing import Optional, List, Dict, Any
from datetime import datetime
from uuid import UUID

{% for dto in dtos %}
# =============================================================================
# {{ dto.name }} DTO
# =============================================================================

class {{ dto.name }}(BaseModel):
    """{{ dto.name }} data transfer object."""

    model_config = {"populate_by_name": True}

    {% for field in dto.fields %}
    {% if field.alias %}
    {{ field.name }}: {{ field.type }} = Field(
        ...,
        alias="{{ field.alias }}",
        serialization_alias="{{ field.alias }}"
    )
    {% else %}
    {{ field.name }}: {{ field.type }}
    {% endif %}
    {% endfor %}

{% endfor %}
# =============================================================================
# Endpoint Constants
# =============================================================================

ENDPOINTS = {
{% for endpoint in endpoints %}
    "{{ endpoint.key }}": "{{ endpoint.path }}",
{% endfor %}
}

# =============================================================================
# Test Data
# =============================================================================

TEST_DATA = {
{% for flow in flows %}
    "{{ flow.name | lower | replace(' ', '_') }}": {
{% for step in flow.steps %}
{% if step.endpoint.request_dto %}
        "{{ step.endpoint.name }}": {
{% for field_name, field_value in step.test_data.items() %}
            "{{ field_name }}": {{ field_value | tojson }},
{% endfor %}
        },
{% endif %}
{% endfor %}
    },
{% endfor %}
}

# =============================================================================
# Validation Rules
# =============================================================================

VALIDATION_RULES = {
{% for dto in dtos %}
    "{{ dto.name }}": {
{% for field in dto.fields %}
        "{{ field.name }}": {
            "type": "{{ field.type }}",
            "required": {{ field.required | tojson }},
            {% if field.validations %}
            "validations": [
{% for validation in field.validations %}
                {
                    "rule_type": "{{ validation.rule_type }}",
                    "value": {{ validation.value | tojson }},
                },
{% endfor %}
            ],
            {% endif %}
        },
{% endfor %}
    },
{% endfor %}
}
'''


# Template for generating service_page.py
PAGE_TEMPLATE = '''"""Page object for {{ service_name }} API.

Auto-generated by socialseed_e2e Flow-Based Test Generator.
Generated at: {{ generation_timestamp }}
"""
from typing import Optional, Dict, Any
from playwright.sync_api import APIResponse

from socialseed_e2e import BasePage
from socialseed_e2e.utils.state_management import DynamicStateMixin

from services.{{ service_name }}.data_schema import (
    ENDPOINTS,
{% for dto in dtos %}
    {{ dto.name }},
{% endfor %}
)


class {{ service_class_name }}Page(BasePage, DynamicStateMixin):
    """Page object for {{ service_name }} service API.

    Auto-generated test page with flow-based methods.
    """

    def __init__(self, base_url: str, **kwargs):
        """Initialize the page.

        Args:
            base_url: Base URL for the service
            **kwargs: Additional arguments for BasePage
        """
        super().__init__(base_url=base_url, **kwargs)
        DynamicStateMixin.__init__(self)

        # Authentication state
        self.access_token: Optional[str] = None
        self.refresh_token: Optional[str] = None
        self.current_user: Optional[Dict[str, Any]] = None

        # Entity state for flows
{% for flow in flows %}
        self.{{ flow.name | lower | replace(' ', '_') }}_state: Dict[str, Any] = {}
{% endfor %}

    def _get_headers(self, extra: Optional[Dict] = None) -> Dict[str, str]:
        """Build headers with authentication if available.

        Args:
            extra: Additional headers to include

        Returns:
            Dictionary of headers
        """
        headers = {"Content-Type": "application/json"}
        if self.access_token:
            headers["Authorization"] = f"Bearer {self.access_token}"
        if extra:
            headers.update(extra)
        return headers

{% for endpoint in endpoints %}
    def do_{{ endpoint.method_name }}(self{% if endpoint.request_dto %}, request: {{ endpoint.request_dto }}{% endif %}{% if endpoint.path_params %}, {{ endpoint.path_params | join(', ') }}{% endif %}) -> APIResponse:
        """{{ endpoint.description }}.

{% if endpoint.request_dto %}
        Args:
            request: {{ endpoint.request_dto }} object
{% endif %}
{% if endpoint.path_params %}
        Args:
{% for param in endpoint.path_params %}
            {{ param }}: Path parameter
{% endfor %}
{% endif %}
        Returns:
            APIResponse from the endpoint
        """
{% if endpoint.path_params %}
        path = ENDPOINTS["{{ endpoint.key }}"].format({{ endpoint.path_params | join(', ') }})
{% else %}
        path = ENDPOINTS["{{ endpoint.key }}"]
{% endif %}

        response = self.{{ endpoint.method | lower }}(
            path,
{% if endpoint.request_dto %}
            data=request.model_dump(by_alias=True),
{% endif %}
            headers=self._get_headers()
        )

        return response

{% endfor %}
{% for flow in flows %}
    # ==========================================================================
    # {{ flow.name }} Flow Methods
    # ==========================================================================

    def run_{{ flow.name | lower | replace(' ', '_') }}_flow(self, test_data: Optional[Dict] = None) -> Dict[str, Any]:
        """Execute the complete {{ flow.name }} flow.

        This method executes all steps in the flow and maintains state
        between steps.

        Args:
            test_data: Optional test data to use (generated if not provided)

        Returns:
            Dictionary with flow results and final state
        """
        results = {
            "flow_name": "{{ flow.name }}",
            "steps": [],
            "success": True,
        }

        if test_data is None:
            test_data = self._get_{{ flow.name | lower | replace(' ', '_') }}_test_data()

{% for step in flow.steps %}
        # Step {{ step.step_number }}: {{ step.description }}
        try:
{% if step.endpoint.request_dto %}
            request_data = test_data.get("{{ step.endpoint.name }}", {})
            request = {{ step.endpoint.request_dto }}(**request_data)
            response = self.do_{{ step.endpoint.method_name }}(request)
{% else %}
            response = self.do_{{ step.endpoint.method_name }}()
{% endif %}

            results["steps"].append({
                "step": {{ step.step_number }},
                "endpoint": "{{ step.endpoint.name }}",
                "status": response.status,
                "success": response.ok,
            })

            if not response.ok:
                results["success"] = False
                return results

            # Store response data for subsequent steps
            response_data = response.json() if response.status == 200 else {}
            self._update_flow_state("{{ flow.name | lower | replace(' ', '_') }}", response_data)

        except Exception as e:
            results["steps"].append({
                "step": {{ step.step_number }},
                "endpoint": "{{ step.endpoint.name }}",
                "error": str(e),
                "success": False,
            })
            results["success"] = False
            return results

{% endfor %}
        return results

    def _get_{{ flow.name | lower | replace(' ', '_') }}_test_data(self) -> Dict[str, Any]:
        """Generate test data for {{ flow.name }} flow."""
        return {
{% for step in flow.steps %}
{% if step.endpoint.request_dto %}
            "{{ step.endpoint.name }}": {
{% for field_name, field_value in step.test_data.items() %}
                "{{ field_name }}": {{ field_value | tojson }},
{% endfor %}
            },
{% endif %}
{% endfor %}
        }

{% endfor %}
    def _update_flow_state(self, flow_name: str, data: Dict[str, Any]) -> None:
        """Update the state for a specific flow.

        Args:
            flow_name: Name of the flow
            data: Data to store in state
        """
        if hasattr(self, f"{flow_name}_state"):
            getattr(self, f"{flow_name}_state").update(data)
'''


# Template for generating test modules
TEST_MODULE_TEMPLATE = '''"""Test module: {{ test_module_name }}

Auto-generated by socialseed_e2e Flow-Based Test Generator.
Flow: {{ flow.name }}
Generated at: {{ generation_timestamp }}
"""
from playwright.sync_api import APIResponse

from services.{{ service_name }}.{{ service_name }}_page import {{ service_class_name }}Page
from services.{{ service_name }}.data_schema import (
{% for dto in flow.get_required_dtos() %}
    {{ dto }},
{% endfor %}
)


def run(page: {{ service_class_name }}Page) -> APIResponse:
    """Execute {{ flow.name }} test flow.

    This test covers:
{% for step in flow.steps %}
    - Step {{ step.step_number }}: {{ step.description }}
{% endfor %}

    Args:
        page: Instance of {{ service_class_name }}Page

    Returns:
        APIResponse from the final step
    """
    print(f"\\n{'='*60}")
    print(f"Running Flow: {{ flow.name }}")
    print(f"{'='*60}\\n")

    # Execute the complete flow
    results = page.run_{{ flow.name | lower | replace(' ', '_') }}_flow()

    # Verify flow completed successfully
    assert results["success"], f"Flow failed at step: {results['steps'][-1]}"

    print(f"\\n✅ {{ flow.name }} completed successfully!")
    print(f"   Steps executed: {len(results['steps'])}")

    # Return the last response
{% if flow.steps %}
{% if flow.steps[-1].endpoint.request_dto %}
    # Re-execute last step to get the response
    request_data = results.get("test_data", {}).get("{{ flow.steps[-1].endpoint.name }}", {})
    request = {{ flow.steps[-1].endpoint.request_dto }}(**request_data)
    return page.do_{{ flow.steps[-1].endpoint.method_name }}(request)
{% else %}
    return page.do_{{ flow.steps[-1].endpoint.method_name }}()
{% endif %}
{% endif %}
'''


@dataclass
class GeneratedTestSuite:
    """Result of test suite generation."""

    service_name: str
    data_schema_code: str
    page_class_code: str
    test_modules: Dict[str, str]  # filename -> code
    flows_detected: List[BusinessFlow]
    generation_timestamp: str


class FlowBasedTestSuiteGenerator:
    """Generator for flow-based test suites."""

    def __init__(self, service_info: ServiceInfo, db_schema: Optional[DatabaseSchema] = None):
        """Initialize the test suite generator.

        Args:
            service_info: Service information with endpoints and DTOs
            db_schema: Optional database schema for constraint-aware generation
        """
        self.service_info = service_info
        self.db_schema = db_schema

        # Initialize inference engine
        self.inference_engine = BusinessLogicInferenceEngine(
            endpoints=service_info.endpoints, dtos=service_info.dto_schemas
        )

        # Initialize dummy data generator
        self.data_generator = DummyDataGenerator(db_schema=db_schema)

        # Run analysis
        self.analysis_result = self.inference_engine.analyze()
        self.flows = self.analysis_result["flows"]

    def generate_test_suite(self) -> GeneratedTestSuite:
        """Generate complete test suite for the service.

        Returns:
            GeneratedTestSuite with all generated code
        """
        timestamp = datetime.now().isoformat()

        # Generate data schema
        data_schema_code = self._generate_data_schema(timestamp)

        # Generate page class
        page_class_code = self._generate_page_class(timestamp)

        # Generate test modules for each flow
        test_modules = {}
        for i, flow in enumerate(self.flows, 1):
            module_code = self._generate_test_module(flow, i, timestamp)
            filename = f"_{i:02d}_{flow.name.lower().replace(' ', '_')}_flow.py"
            test_modules[filename] = module_code

        # Generate validation test module
        validation_module = self._generate_validation_test_module(timestamp)
        if validation_module:
            test_modules["_99_validation_tests.py"] = validation_module

        return GeneratedTestSuite(
            service_name=self.service_info.name,
            data_schema_code=data_schema_code,
            page_class_code=page_class_code,
            test_modules=test_modules,
            flows_detected=self.flows,
            generation_timestamp=timestamp,
        )

    def _generate_data_schema(self, timestamp: str) -> str:
        """Generate data_schema.py content."""
        template = Template(DATA_SCHEMA_TEMPLATE)

        # Prepare DTOs
        dtos = []
        for dto in self.service_info.dto_schemas:
            dtos.append({"name": dto.name, "fields": dto.fields})

        # Prepare endpoints with keys
        endpoints = []
        for ep in self.service_info.endpoints:
            key = self._generate_endpoint_key(ep)
            endpoints.append(
                {
                    "key": key,
                    "path": ep.path,
                }
            )

        # Prepare flows with test data
        flows_data = []
        for flow in self.flows:
            flow_data = {"name": flow.name, "steps": []}
            for step in flow.steps:
                step_data = {"endpoint": step.endpoint, "test_data": {}}

                # Generate test data for this step
                if step.endpoint.request_dto:
                    dto = next(
                        (
                            d
                            for d in self.service_info.dto_schemas
                            if d.name == step.endpoint.request_dto
                        ),
                        None,
                    )
                    if dto:
                        generated = self.data_generator.generate_for_dto(dto)
                        step_data["test_data"] = {k: v.value for k, v in generated.items()}

                flow_data["steps"].append(step_data)

            flows_data.append(flow_data)

        return template.render(
            service_name=self.service_info.name,
            dtos=dtos,
            endpoints=endpoints,
            flows=flows_data,
            generation_timestamp=timestamp,
        )

    def _generate_page_class(self, timestamp: str) -> str:
        """Generate service page class."""
        template = Template(PAGE_TEMPLATE)

        # Prepare endpoints with method names
        endpoints = []
        for ep in self.service_info.endpoints:
            method_name = self._generate_method_name(ep)
            key = self._generate_endpoint_key(ep)

            # Extract path parameters
            path_params = re.findall(r"\{(\w+)\}", ep.path)

            endpoints.append(
                {
                    "method_name": method_name,
                    "key": key,
                    "method": ep.method.value,
                    "path": ep.path,
                    "path_params": path_params,
                    "request_dto": ep.request_dto,
                    "description": ep.description or f"{ep.method.value} {ep.path}",
                }
            )

        # Prepare flows with test data
        flows_data = []
        for flow in self.flows:
            flow_data = {
                "name": flow.name,
                "description": flow.description,
                "steps": [],
            }
            for step in flow.steps:
                step_data = {
                    "step_number": step.step_number,
                    "description": step.description,
                    "endpoint": {
                        "name": step.endpoint.name,
                        "method_name": self._generate_method_name(step.endpoint),
                        "request_dto": step.endpoint.request_dto,
                    },
                    "test_data": {},
                }

                # Generate test data
                if step.endpoint.request_dto:
                    dto = next(
                        (
                            d
                            for d in self.service_info.dto_schemas
                            if d.name == step.endpoint.request_dto
                        ),
                        None,
                    )
                    if dto:
                        generated = self.data_generator.generate_for_dto(dto)
                        step_data["test_data"] = {k: v.value for k, v in generated.items()}

                flow_data["steps"].append(step_data)

            flows_data.append(flow_data)

        # Prepare DTOs
        dtos = [{"name": d.name} for d in self.service_info.dto_schemas]

        return template.render(
            service_name=self.service_info.name,
            service_class_name=self._to_pascal_case(self.service_info.name),
            dtos=dtos,
            endpoints=endpoints,
            flows=flows_data,
            generation_timestamp=timestamp,
        )

    def _generate_test_module(self, flow: BusinessFlow, index: int, timestamp: str) -> str:
        """Generate a test module for a specific flow."""
        template = Template(TEST_MODULE_TEMPLATE)

        return template.render(
            test_module_name=flow.name,
            service_name=self.service_info.name,
            service_class_name=self._to_pascal_case(self.service_info.name),
            flow=flow,
            generation_timestamp=timestamp,
        )

    def _generate_validation_test_module(self, timestamp: str) -> Optional[str]:
        """Generate validation and chaos test module."""
        if not self.service_info.dto_schemas:
            return None

        lines = [
            '"""Validation and Chaos Tests',
            "",
            "Auto-generated validation tests covering:",
            "- Required field validation",
            "- Type validation",
            "- Boundary value testing",
            "- Chaos/random data testing",
            "",
            f"Generated at: {timestamp}",
            '"""',
            "import pytest",
            "from playwright.sync_api import APIResponse",
            "",
            f"from services.{self.service_info.name}.{self.service_info.name}_page import {self._to_pascal_case(self.service_info.name)}Page",
            f"from services.{self.service_info.name}.data_schema import (",
        ]

        # Import DTOs
        for dto in self.service_info.dto_schemas:
            lines.append(f"    {dto.name},")
        lines.append(")")
        lines.append("")

        # Generate validation tests for each DTO
        for dto in self.service_info.dto_schemas:
            scenarios = self.data_generator.generate_test_scenarios(dto)

            for scenario in scenarios:
                test_name = scenario["name"].replace(" ", "_").replace("-", "_").lower()
                lines.append(f"")
                lines.append(
                    f"def test_{test_name}(page: {self._to_pascal_case(self.service_info.name)}Page):"
                )
                lines.append(f'    """{scenario["description"]}"""')
                lines.append(f"    # Test data: {scenario['type']}")
                lines.append(f"    data = {scenario['data']}")
                lines.append(f"    ")
                lines.append(f"    # TODO: Implement test logic")
                lines.append(f"    # Expected result: {scenario['expected']}")
                lines.append(f"    pass")
                lines.append(f"")

        return "\n".join(lines)

    def _generate_method_name(self, endpoint: EndpointInfo) -> str:
        """Generate Python method name from endpoint."""
        prefix = "do_"

        # Map HTTP methods to action verbs
        method_map = {
            "GET": "get",
            "POST": "create",
            "PUT": "update",
            "PATCH": "patch",
            "DELETE": "delete",
        }

        action = method_map.get(endpoint.method.value, endpoint.method.value.lower())

        # Extract resource name from endpoint name
        name = endpoint.name
        name = re.sub(r"^(get|post|put|delete|patch)", "", name, flags=re.IGNORECASE)
        name = re.sub(r"([a-z])([A-Z])", r"\1_\2", name).lower()
        name = re.sub(r"[^a-z0-9_]", "_", name)

        return f"{prefix}{action}_{name}".strip("_")

    def _generate_endpoint_key(self, endpoint: EndpointInfo) -> str:
        """Generate a key for the ENDPOINTS dictionary."""
        method_map = {
            "GET": "get",
            "POST": "create",
            "PUT": "update",
            "PATCH": "patch",
            "DELETE": "delete",
        }

        action = method_map.get(endpoint.method.value, endpoint.method.value.lower())
        name = endpoint.name.lower()
        name = re.sub(r"^(get|post|put|delete|patch)", "", name, flags=re.IGNORECASE)
        name = re.sub(r"([a-z])([A-Z])", r"\1_\2", name).lower()

        return f"{action}_{name}".strip("_").replace("__", "_")

    def _to_pascal_case(self, name: str) -> str:
        """Convert service name to PascalCase."""
        return "".join(word.capitalize() for word in name.replace("-", "_").split("_"))

    def write_to_files(self, output_dir: Path) -> None:
        """Write generated test suite to files.

        Args:
            output_dir: Directory to write files to
        """
        suite = self.generate_test_suite()

        # Create service directory
        service_dir = output_dir / self.service_info.name
        service_dir.mkdir(parents=True, exist_ok=True)

        # Create modules directory
        modules_dir = service_dir / "modules"
        modules_dir.mkdir(exist_ok=True)

        # Write data_schema.py
        (service_dir / "data_schema.py").write_text(suite.data_schema_code)

        # Write page class
        page_filename = f"{self.service_info.name}_page.py"
        (service_dir / page_filename).write_text(suite.page_class_code)

        # Write __init__.py
        (service_dir / "__init__.py").write_text(f'"""{self.service_info.name} service tests."""\n')
        (modules_dir / "__init__.py").write_text('"""Test modules."""\n')

        # Write test modules
        for filename, code in suite.test_modules.items():
            (modules_dir / filename).write_text(code)

        print(f"✅ Generated test suite for '{self.service_info.name}'")
        print(f"   Location: {service_dir}")
        print(f"   Flows detected: {len(suite.flows_detected)}")
        for flow in suite.flows_detected:
            print(f"   - {flow.name} ({len(flow.steps)} steps)")
