"""
Tests for MarkiTect User Profile Management System.

This module tests the complete user profile management functionality including:
- CRUD operations for user profiles
- Profile validation and schema compliance
- Database integration and data persistence
- Profile inheritance and merging
- Template variable extraction
- Export/import functionality
"""

import pytest
import tempfile
import os
import json
from datetime import datetime
from pathlib import Path

from markitect.profile.manager import ProfileManager, ProfileNotFoundError, ProfileValidationError
from markitect.profile.schema import ProfileSchema, ProfileData, ContactInfo, Address, Organization


class TestProfileSchema:
    """Test suite for profile schema and validation."""

    def test_profile_data_creation(self):
        """Test ProfileData dataclass creation."""
        profile = ProfileData(
            first_name="John",
            last_name="Doe",
            contact=ContactInfo(email="john@example.com"),
            address=Address(city="Boston", country="USA")
        )

        assert profile.first_name == "John"
        assert profile.last_name == "Doe"
        assert profile.contact.email == "john@example.com"
        assert profile.address.city == "Boston"

    def test_profile_data_to_dict(self):
        """Test converting ProfileData to dictionary."""
        profile = ProfileData(
            first_name="Jane",
            last_name="Smith",
            contact=ContactInfo(email="jane@example.com", phone="123-456-7890")
        )

        profile_dict = profile.to_dict()

        assert profile_dict["first_name"] == "Jane"
        assert profile_dict["last_name"] == "Smith"
        assert profile_dict["contact"]["email"] == "jane@example.com"
        assert profile_dict["contact"]["phone"] == "123-456-7890"

    def test_profile_data_from_dict(self):
        """Test creating ProfileData from dictionary."""
        data = {
            "first_name": "Bob",
            "last_name": "Johnson",
            "contact": {
                "email": "bob@example.com",
                "phone": "098-765-4321"
            },
            "organization": {
                "name": "ACME Corp",
                "position": "Developer"
            }
        }

        profile = ProfileData.from_dict(data)

        assert profile.first_name == "Bob"
        assert profile.contact.email == "bob@example.com"
        assert profile.organization.name == "ACME Corp"

    def test_profile_schema_validation_success(self):
        """Test successful profile schema validation."""
        valid_data = {
            "first_name": "Alice",
            "last_name": "Wilson",
            "contact": {
                "email": "alice@example.com"
            },
            "address": {
                "city": "New York",
                "country": "USA"
            }
        }

        # Should not raise exception
        ProfileSchema.validate(valid_data)
        assert ProfileSchema.is_valid(valid_data) is True

    def test_profile_schema_validation_failure(self):
        """Test profile schema validation with invalid data."""
        invalid_data = {
            "first_name": "A" * 150,  # Too long
            "contact": {
                "email": "invalid-email"  # Invalid email format
            }
        }

        with pytest.raises(Exception):  # ValidationError
            ProfileSchema.validate(invalid_data)

        assert ProfileSchema.is_valid(invalid_data) is False

    def test_profile_schema_get_field_description(self):
        """Test getting field descriptions from schema."""
        email_desc = ProfileSchema.get_field_description("contact.email")
        assert "Email address" in email_desc

        name_desc = ProfileSchema.get_field_description("first_name")
        assert "first name" in name_desc.lower()

        invalid_desc = ProfileSchema.get_field_description("nonexistent.field")
        assert invalid_desc is None

    def test_profile_schema_get_all_fields(self):
        """Test getting all available field paths."""
        fields = ProfileSchema.get_all_fields()

        assert "first_name" in fields
        assert "contact.email" in fields
        assert "organization.name" in fields
        assert "address.city" in fields
        assert len(fields) > 10  # Should have many fields

    def test_create_empty_profile(self):
        """Test creating empty profile with timestamps."""
        profile = ProfileSchema.create_empty_profile()

        assert profile.first_name is None
        assert profile.contact is not None
        assert profile.created_at is not None
        assert profile.updated_at is not None


class TestProfileManager:
    """Test suite for profile manager functionality."""

    @pytest.fixture
    def temp_db(self):
        """Create temporary database for testing."""
        fd, path = tempfile.mkstemp(suffix='.db')
        os.close(fd)
        yield path
        os.unlink(path)

    @pytest.fixture
    def profile_manager(self, temp_db):
        """Create profile manager with temporary database."""
        return ProfileManager(temp_db)

    @pytest.fixture
    def sample_profile_data(self):
        """Sample profile data for testing."""
        return ProfileData(
            first_name="John",
            last_name="Doe",
            full_name="John Doe",
            contact=ContactInfo(
                email="john.doe@example.com",
                phone="555-0123"
            ),
            organization=Organization(
                name="Tech Corp",
                position="Senior Developer"
            ),
            address=Address(
                city="San Francisco",
                state="CA",
                country="USA"
            )
        )

    def test_create_profile_success(self, profile_manager, sample_profile_data):
        """Test successful profile creation."""
        profile_id = profile_manager.create_profile(
            name="personal",
            data=sample_profile_data,
            description="My personal profile"
        )

        assert profile_id is not None
        assert isinstance(profile_id, int)

        # Verify profile was created
        retrieved_profile = profile_manager.get_profile("personal")
        assert retrieved_profile.first_name == "John"
        assert retrieved_profile.last_name == "Doe"
        assert retrieved_profile.contact.email == "john.doe@example.com"

    def test_create_profile_with_dict(self, profile_manager):
        """Test profile creation with dictionary data."""
        profile_data = {
            "first_name": "Jane",
            "last_name": "Smith",
            "contact": {
                "email": "jane@example.com"
            }
        }

        profile_id = profile_manager.create_profile("work", profile_data)
        assert profile_id is not None

        retrieved_profile = profile_manager.get_profile("work")
        assert retrieved_profile.first_name == "Jane"

    def test_create_profile_duplicate_name(self, profile_manager, sample_profile_data):
        """Test creating profile with duplicate name fails."""
        profile_manager.create_profile("test", sample_profile_data)

        with pytest.raises(ValueError, match="already exists"):
            profile_manager.create_profile("test", sample_profile_data)

    def test_create_profile_invalid_data(self, profile_manager):
        """Test creating profile with invalid data fails."""
        invalid_data = {
            "first_name": "A" * 150,  # Too long
            "contact": {
                "email": "invalid-email"
            }
        }

        with pytest.raises(ProfileValidationError):
            profile_manager.create_profile("invalid", invalid_data)

    def test_create_profile_set_default(self, profile_manager, sample_profile_data):
        """Test creating profile and setting as default."""
        profile_id = profile_manager.create_profile(
            "default_test",
            sample_profile_data,
            set_as_default=True
        )

        # Verify it's set as default
        default_profile = profile_manager.get_default_profile()
        assert default_profile is not None
        assert default_profile.first_name == "John"

    def test_get_profile_not_found(self, profile_manager):
        """Test getting non-existent profile raises error."""
        with pytest.raises(ProfileNotFoundError):
            profile_manager.get_profile("nonexistent")

    def test_get_profile_info(self, profile_manager, sample_profile_data):
        """Test getting profile metadata."""
        profile_manager.create_profile("info_test", sample_profile_data, description="Test profile")

        profile_info = profile_manager.get_profile_info("info_test")

        assert profile_info["name"] == "info_test"
        assert profile_info["description"] == "Test profile"
        assert profile_info["is_active"] is True
        assert profile_info["is_default"] is False
        assert "created_at" in profile_info
        assert "updated_at" in profile_info

    def test_update_profile_success(self, profile_manager, sample_profile_data):
        """Test successful profile update."""
        profile_manager.create_profile("update_test", sample_profile_data)

        # Update some fields
        updated_data = ProfileData(
            first_name="Johnny",
            last_name="Doe",
            contact=ContactInfo(email="johnny@example.com")
        )

        success = profile_manager.update_profile("update_test", updated_data, "Updated description")
        assert success is True

        # Verify updates
        updated_profile = profile_manager.get_profile("update_test")
        assert updated_profile.first_name == "Johnny"
        assert updated_profile.contact.email == "johnny@example.com"

        profile_info = profile_manager.get_profile_info("update_test")
        assert profile_info["description"] == "Updated description"

    def test_update_profile_not_found(self, profile_manager):
        """Test updating non-existent profile fails."""
        with pytest.raises(ProfileNotFoundError):
            profile_manager.update_profile("nonexistent", ProfileData())

    def test_delete_profile_soft_delete(self, profile_manager, sample_profile_data):
        """Test soft delete (deactivate) profile."""
        profile_manager.create_profile("delete_test", sample_profile_data)

        success = profile_manager.delete_profile("delete_test", hard_delete=False)
        assert success is True

        # Profile should not be found in active profiles
        with pytest.raises(ProfileNotFoundError):
            profile_manager.get_profile("delete_test")

        # But should appear in list with inactive profiles
        all_profiles = profile_manager.list_profiles(include_inactive=True)
        inactive_names = [p["name"] for p in all_profiles if not p["is_active"]]
        assert "delete_test" in inactive_names

    def test_delete_profile_hard_delete(self, profile_manager, sample_profile_data):
        """Test hard delete (permanent) profile."""
        profile_manager.create_profile("hard_delete_test", sample_profile_data)

        success = profile_manager.delete_profile("hard_delete_test", hard_delete=True)
        assert success is True

        # Profile should not appear anywhere
        all_profiles = profile_manager.list_profiles(include_inactive=True)
        all_names = [p["name"] for p in all_profiles]
        assert "hard_delete_test" not in all_names

    def test_list_profiles_active_only(self, profile_manager, sample_profile_data):
        """Test listing active profiles only."""
        # Create multiple profiles
        profile_manager.create_profile("active1", sample_profile_data)
        profile_manager.create_profile("active2", sample_profile_data)
        profile_manager.create_profile("to_deactivate", sample_profile_data)

        # Deactivate one
        profile_manager.delete_profile("to_deactivate", hard_delete=False)

        profiles = profile_manager.list_profiles(include_inactive=False)
        active_names = [p["name"] for p in profiles]

        assert "active1" in active_names
        assert "active2" in active_names
        assert "to_deactivate" not in active_names

    def test_list_profiles_include_inactive(self, profile_manager, sample_profile_data):
        """Test listing all profiles including inactive."""
        profile_manager.create_profile("active", sample_profile_data)
        profile_manager.create_profile("inactive", sample_profile_data)
        profile_manager.delete_profile("inactive", hard_delete=False)

        profiles = profile_manager.list_profiles(include_inactive=True)
        all_names = [p["name"] for p in profiles]

        assert "active" in all_names
        assert "inactive" in all_names
        assert len(profiles) == 2

    def test_set_default_profile(self, profile_manager, sample_profile_data):
        """Test setting default profile."""
        # Create multiple profiles
        profile_manager.create_profile("profile1", sample_profile_data)
        profile_manager.create_profile("profile2", sample_profile_data)

        # Set profile2 as default
        success = profile_manager.set_default_profile("profile2")
        assert success is True

        # Verify default
        default_profile = profile_manager.get_default_profile()
        assert default_profile is not None
        assert default_profile.first_name == "John"  # From sample data

        # Check that only profile2 is marked as default
        profiles = profile_manager.list_profiles()
        default_profiles = [p for p in profiles if p["is_default"]]
        assert len(default_profiles) == 1
        assert default_profiles[0]["name"] == "profile2"

    def test_get_default_profile_none_set(self, profile_manager):
        """Test getting default profile when none is set."""
        default_profile = profile_manager.get_default_profile()
        assert default_profile is None

    def test_export_profile_json(self, profile_manager, sample_profile_data):
        """Test exporting profile to JSON format."""
        profile_manager.create_profile("export_test", sample_profile_data, "Test for export")

        exported = profile_manager.export_profile("export_test", format="json")

        # Parse and verify
        data = json.loads(exported)
        assert "profile_info" in data
        assert "profile_data" in data
        assert data["profile_info"]["name"] == "export_test"
        assert data["profile_data"]["first_name"] == "John"

    def test_export_profile_yaml(self, profile_manager, sample_profile_data):
        """Test exporting profile to YAML format."""
        profile_manager.create_profile("yaml_test", sample_profile_data)

        try:
            exported = profile_manager.export_profile("yaml_test", format="yaml")
            assert "first_name: John" in exported
            assert "profile_info:" in exported
        except ValueError as e:
            if "PyYAML" in str(e):
                pytest.skip("PyYAML not available")
            raise

    def test_export_profile_unsupported_format(self, profile_manager, sample_profile_data):
        """Test exporting profile with unsupported format."""
        profile_manager.create_profile("format_test", sample_profile_data)

        with pytest.raises(ValueError, match="Unsupported export format"):
            profile_manager.export_profile("format_test", format="xml")

    def test_import_profile_json(self, profile_manager):
        """Test importing profile from JSON."""
        import_data = {
            "profile_info": {
                "description": "Imported profile"
            },
            "profile_data": {
                "first_name": "Imported",
                "last_name": "User",
                "contact": {
                    "email": "imported@example.com"
                }
            }
        }

        json_data = json.dumps(import_data)
        profile_id = profile_manager.import_profile("imported", json_data, format="json")

        assert profile_id is not None

        # Verify imported data
        imported_profile = profile_manager.get_profile("imported")
        assert imported_profile.first_name == "Imported"
        assert imported_profile.contact.email == "imported@example.com"

        profile_info = profile_manager.get_profile_info("imported")
        assert profile_info["description"] == "Imported profile"

    def test_import_profile_overwrite(self, profile_manager, sample_profile_data):
        """Test importing profile with overwrite."""
        # Create existing profile
        profile_manager.create_profile("overwrite_test", sample_profile_data)

        # Import new data
        import_data = {
            "profile_data": {
                "first_name": "Overwritten",
                "contact": {"email": "new@example.com"}
            }
        }

        json_data = json.dumps(import_data)
        profile_id = profile_manager.import_profile("overwrite_test", json_data, overwrite=True)

        # Verify overwrite
        profile = profile_manager.get_profile("overwrite_test")
        assert profile.first_name == "Overwritten"

    def test_import_profile_no_overwrite_fails(self, profile_manager, sample_profile_data):
        """Test importing existing profile without overwrite fails."""
        profile_manager.create_profile("existing", sample_profile_data)

        import_data = {"profile_data": {"first_name": "New"}}
        json_data = json.dumps(import_data)

        with pytest.raises(ValueError, match="already exists"):
            profile_manager.import_profile("existing", json_data, overwrite=False)

    def test_merge_profiles(self, profile_manager):
        """Test merging two profiles."""
        # Create base profile
        base_data = ProfileData(
            first_name="Base",
            last_name="User",
            contact=ContactInfo(email="base@example.com", phone="123-456-7890"),
            address=Address(city="BaseCity")
        )
        profile_manager.create_profile("base", base_data)

        # Create override profile
        override_data = ProfileData(
            first_name="Override",
            contact=ContactInfo(email="override@example.com"),
            organization=Organization(name="Override Corp")
        )
        profile_manager.create_profile("override", override_data)

        # Merge profiles
        merged_profile = profile_manager.merge_profiles("base", "override")

        # Verify merge results
        assert merged_profile.first_name == "Override"  # Overridden
        assert merged_profile.last_name == "User"  # From base
        assert merged_profile.contact.email == "override@example.com"  # Overridden
        assert merged_profile.contact.phone == "123-456-7890"  # From base
        assert merged_profile.address.city == "BaseCity"  # From base
        assert merged_profile.organization.name == "Override Corp"  # From override

    def test_get_template_variables(self, profile_manager, sample_profile_data):
        """Test extracting template variables from profile."""
        profile_manager.create_profile("template_test", sample_profile_data, set_as_default=True)

        variables = profile_manager.get_template_variables("template_test")

        # Check flattened variables
        assert variables["first_name"] == "John"
        assert variables["last_name"] == "Doe"
        assert variables["contact.email"] == "john.doe@example.com"
        assert variables["organization.name"] == "Tech Corp"
        assert variables["address.city"] == "San Francisco"

        # Check computed variable
        assert variables["full_name"] == "John Doe"

    def test_get_template_variables_default_profile(self, profile_manager, sample_profile_data):
        """Test getting template variables from default profile."""
        profile_manager.create_profile("default_vars", sample_profile_data, set_as_default=True)

        # Get variables without specifying profile name
        variables = profile_manager.get_template_variables()

        assert variables["first_name"] == "John"
        assert "contact.email" in variables

    def test_get_template_variables_no_default(self, profile_manager):
        """Test getting template variables when no default profile."""
        variables = profile_manager.get_template_variables()
        assert variables == {}

    def test_database_integration(self, profile_manager, sample_profile_data):
        """Test database persistence and retrieval."""
        # Create profile
        profile_id = profile_manager.create_profile("db_test", sample_profile_data)

        # Create new manager instance with same database
        new_manager = ProfileManager(profile_manager.db_path)

        # Verify data persists
        retrieved_profile = new_manager.get_profile("db_test")
        assert retrieved_profile.first_name == "John"
        assert retrieved_profile.contact.email == "john.doe@example.com"

    def test_profile_timestamps(self, profile_manager, sample_profile_data):
        """Test profile creation and update timestamps."""
        before_create = datetime.now().isoformat()
        profile_manager.create_profile("timestamp_test", sample_profile_data)
        after_create = datetime.now().isoformat()

        profile_info = profile_manager.get_profile_info("timestamp_test")
        assert before_create <= profile_info["created_at"] <= after_create
        assert before_create <= profile_info["updated_at"] <= after_create

        # Update profile
        before_update = datetime.now().isoformat()
        profile_manager.update_profile("timestamp_test", ProfileData(first_name="Updated"))
        after_update = datetime.now().isoformat()

        updated_info = profile_manager.get_profile_info("timestamp_test")
        assert updated_info["created_at"] == profile_info["created_at"]  # Unchanged
        assert before_update <= updated_info["updated_at"] <= after_update

    def test_edge_cases(self, profile_manager):
        """Test edge cases and boundary conditions."""
        # Empty profile
        empty_profile = ProfileData()
        profile_manager.create_profile("empty", empty_profile)
        retrieved = profile_manager.get_profile("empty")
        assert retrieved.first_name is None

        # Profile with only custom fields
        custom_profile = ProfileData(custom_fields={"hobby": "coding", "level": "expert"})
        profile_manager.create_profile("custom", custom_profile)
        retrieved_custom = profile_manager.get_profile("custom")
        assert retrieved_custom.custom_fields["hobby"] == "coding"