import argparse
import json
import logging
import re
import sys
from datetime import datetime
from pathlib import Path
from typing import Dict, Any

import pandas as pd

from amplify_client import AmplifyClient
from amplify_excel_migrator.core import ConfigManager
from amplify_excel_migrator.schema import FieldParser
from amplify_excel_migrator.data import ExcelReader, DataTransformer
from amplify_excel_migrator.migration import FailureTracker, ProgressReporter, BatchUploader, MigrationOrchestrator

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)


class ExcelToAmplifyMigrator:
    def __init__(self, excel_file_path: str):
        self.excel_file_path = excel_file_path
        self.amplify_client = None

        self.field_parser = FieldParser()
        self.excel_reader = ExcelReader(excel_file_path)
        self.data_transformer = DataTransformer(self.field_parser)
        self.failure_tracker = FailureTracker()
        self.progress_reporter = ProgressReporter()
        self.batch_uploader = None

    def init_client(
        self,
        api_endpoint: str,
        region: str,
        user_pool_id: str,
        is_aws_admin: bool = False,
        client_id: str = None,
        username: str = None,
        aws_profile: str = None,
    ):

        self.amplify_client = AmplifyClient(
            api_endpoint=api_endpoint,
            user_pool_id=user_pool_id,
            region=region,
            client_id=client_id,
        )

        self.batch_uploader = BatchUploader(self.amplify_client)

        try:
            self.amplify_client.init_cognito_client(
                is_aws_admin=is_aws_admin, username=username, aws_profile=aws_profile
            )

        except RuntimeError or Exception:
            sys.exit(1)

    def authenticate(self, username: str, password: str) -> bool:
        return self.amplify_client.authenticate(username, password)

    @property
    def failed_records_by_sheet(self) -> Dict[str, list]:
        return self.failure_tracker.get_failures_by_sheet()

    @failed_records_by_sheet.setter
    def failed_records_by_sheet(self, value: Dict[str, list]) -> None:
        self.failure_tracker.clear()
        for sheet_name, failures in value.items():
            self.failure_tracker.set_current_sheet(sheet_name)
            for failure in failures:
                self.failure_tracker.record_failure(
                    primary_field=failure.get("primary_field", ""),
                    primary_field_value=failure.get("primary_field_value", ""),
                    error=failure.get("error", ""),
                    original_row=failure.get("original_row"),
                )

    @property
    def _current_sheet(self) -> str:
        return self.failure_tracker._current_sheet

    @_current_sheet.setter
    def _current_sheet(self, value: str) -> None:
        self.failure_tracker.set_current_sheet(value)

    def run(self):
        all_sheets = self.read_excel()

        total_success = 0

        for sheet_name, df in all_sheets.items():
            logger.info(f"Processing {sheet_name} sheet with {len(df)} rows")
            total_success += self.process_sheet(df, sheet_name)

        self._display_summary(len(all_sheets), total_success)

    def _display_summary(self, sheets_processed: int, total_success: int) -> None:
        total_failed = sum(len(failures) for failures in self.failed_records_by_sheet.values())

        print("\n" + "=" * 60)
        print("MIGRATION SUMMARY")
        print("=" * 60)
        print(f"📊 Sheets processed: {sheets_processed}")
        print(f"✅ Total successful: {total_success}")
        print(f"❌ Total failed: {total_failed}")
        print(
            f"📈 Success rate: {(total_success / (total_success + total_failed) * 100):.1f}%"
            if (total_success + total_failed) > 0
            else "📈 Success rate: N/A"
        )

        if total_failed > 0:
            print("\n" + "=" * 60)
            print("FAILED RECORDS DETAILS")
            print("=" * 60)

            for sheet_name, failed_records in self.failed_records_by_sheet.items():
                if not failed_records:
                    continue

                print(f"\n📄 {sheet_name}:")
                print("-" * 60)
                for record in failed_records:
                    primary_field_value = record.get("primary_field_value", "Unknown")
                    error = record.get("error", "Unknown error")
                    print(f"  • Record: {primary_field_value}")
                    print(f"    Error: {error}")

            print("\n" + "=" * 60)

            export_confirm = input("\nExport failed records to Excel? (yes/no): ")
            if export_confirm.lower() == "yes":
                failed_records_file = self._write_failed_records_to_excel()
                if failed_records_file:
                    print(f"📁 Failed records exported to: {failed_records_file}")
                    print("=" * 60)

                    update_config = input("\nUpdate config to use this failed records file for next run? (yes/no): ")
                    if update_config.lower() == "yes":
                        self._update_excel_path_in_config(failed_records_file)
                        print(f"✅ Config updated! Next 'migrate' will use: {Path(failed_records_file).name}")
                        print("=" * 60)
            else:
                print("Failed records export skipped.")
                print("=" * 60)
        else:
            print("\n✨ No failed records!")

        print("=" * 60)

    @staticmethod
    def _update_excel_path_in_config(new_excel_path: str) -> None:
        config_manager = ConfigManager()
        config_manager.update({"excel_path": new_excel_path})

    def _write_failed_records_to_excel(self) -> str | None:
        if not self.failed_records_by_sheet or all(
            len(failures) == 0 for failures in self.failed_records_by_sheet.values()
        ):
            return None

        input_path = Path(self.excel_file_path)

        base_name = input_path.stem
        if "_failed_records_" in base_name:
            base_name = base_name.split("_failed_records_")[0]

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_filename = f"{base_name}_failed_records_{timestamp}.xlsx"
        output_path = input_path.parent / output_filename

        logger.info(f"Writing failed records to {output_path}")

        with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
            for sheet_name, failed_records in self.failed_records_by_sheet.items():
                if not failed_records:
                    continue

                rows_data = []
                for record in failed_records:
                    row_data = record.get("original_row", {}).copy()
                    row_data["ERROR"] = record["error"]
                    rows_data.append(row_data)

                df = pd.DataFrame(rows_data)

                df.to_excel(writer, sheet_name=sheet_name, index=False)

        logger.info(f"Successfully wrote failed records to {output_path}")
        return str(output_path)

    def read_excel(self) -> Dict[str, Any]:
        return self.excel_reader.read_all_sheets()

    def process_sheet(self, df: pd.DataFrame, sheet_name: str) -> int:
        self._current_sheet = sheet_name

        parsed_model_structure = self.get_parsed_model_structure(sheet_name)

        records, row_dict_by_primary = self.transform_rows_to_records(df, parsed_model_structure, sheet_name)

        confirm = input(f"\nUpload {len(records)} records of {sheet_name} to Amplify? (yes/no): ")
        if confirm.lower() != "yes":
            logger.info(f"Upload cancelled for {sheet_name} sheet")
            return 0

        success_count, upload_error_count, failed_uploads = self.amplify_client.upload(
            records, sheet_name, parsed_model_structure
        )

        for failed_upload in failed_uploads:
            primary_value = str(failed_upload["primary_field_value"])
            original_row = row_dict_by_primary.get(primary_value, {})

            self._record_failure(
                primary_field=failed_upload["primary_field"],
                primary_field_value=failed_upload["primary_field_value"],
                error=failed_upload["error"],
                original_row=original_row,
            )

        print(f"=== Upload of Excel sheet: {sheet_name} Complete ===")
        print(f"✅ Success: {success_count}")
        print(
            f"❌ Failed: {len(self.failed_records_by_sheet[sheet_name])} "
            f"(Parsing: {len(self.failed_records_by_sheet[sheet_name])}, Upload: {upload_error_count})"
        )
        print(f"📊 Total: {len(df)}")

        return success_count

    def transform_rows_to_records(
        self,
        df: pd.DataFrame,
        parsed_model_structure: Dict[str, Any],
        sheet_name: str,
    ) -> tuple[list[Any], Dict[str, Dict]]:
        df.columns = [self.data_transformer.to_camel_case(c) for c in df.columns]
        primary_field, _, _ = self.amplify_client.get_primary_field_name(sheet_name, parsed_model_structure)

        fk_lookup_cache = {}
        if self.amplify_client:
            logger.info("🚀 Pre-fetching foreign key lookups...")
            fk_lookup_cache = self.amplify_client.build_foreign_key_lookups(df, parsed_model_structure)

        records, row_dict_by_primary, failed_rows = self.data_transformer.transform_rows_to_records(
            df, parsed_model_structure, primary_field, fk_lookup_cache
        )

        for failed_row in failed_rows:
            self._record_failure(
                primary_field=failed_row["primary_field"],
                primary_field_value=failed_row["primary_field_value"],
                error=failed_row["error"],
                original_row=failed_row["original_row"],
            )

        return records, row_dict_by_primary

    def get_parsed_model_structure(self, sheet_name: str) -> Dict[str, Any]:
        model_structure = self.amplify_client.get_model_structure(sheet_name)
        return self.field_parser.parse_model_structure(model_structure)

    def _record_failure(
        self,
        primary_field: str,
        primary_field_value: str,
        error: str,
        original_row: Dict = None,
    ) -> None:
        self.failure_tracker.record_failure(primary_field, primary_field_value, error, original_row)

    def transform_row_to_record(
        self, row_dict: Dict, parsed_model_structure: Dict[str, Any], fk_lookup_cache: Dict[str, Dict[str, str]]
    ) -> dict[Any, Any] | None:
        """Transform a DataFrame row to Amplify model format"""

        model_record = {}

        for field in parsed_model_structure["fields"]:
            input = self.parse_input(row_dict, field, parsed_model_structure, fk_lookup_cache)
            if input:
                model_record[field["name"]] = input

        return model_record

    def parse_input(
        self,
        row_dict: Dict,
        field: Dict[str, Any],
        parsed_model_structure: Dict[str, Any],
        fk_lookup_cache: Dict[str, Dict[str, str]],
    ) -> Any | None:
        field_name = field["name"][:-2] if field["is_id"] else field["name"]

        if field_name not in row_dict or pd.isna(row_dict[field_name]):
            if field["is_required"]:
                raise ValueError(f"Required field '{field_name}' is missing")
            return None

        value = self.field_parser.clean_input(row_dict[field_name])

        if field["is_id"]:
            if "related_model" in field:
                related_model = field["related_model"]
            else:
                related_model = (temp := field["name"][:-2])[0].upper() + temp[1:]

            if related_model in fk_lookup_cache:
                lookup_dict = fk_lookup_cache[related_model]["lookup"]
                record_id = lookup_dict.get(str(value))

                if record_id:
                    return record_id
                elif field["is_required"]:
                    raise ValueError(f"{related_model}: {value} does not exist")
                return None
            else:
                logger.warning(f"No pre-fetched data for {related_model}, falling back to API call")
                record = self.amplify_client.get_record(
                    related_model, parsed_model_structure=parsed_model_structure, value=value
                )
                if record and record.get("id"):
                    return record["id"]
                elif field["is_required"]:
                    raise ValueError(f"{related_model}: {value} does not exist")
                return None
        elif field["is_list"] and field["is_scalar"]:
            return self.field_parser.parse_scalar_array(field, field_name, row_dict[field_name])
        else:
            return self.field_parser.parse_field_input(field, field_name, value)

    def _parse_custom_type_array(self, row: pd.Series, field: Dict[str, Any]) -> Any:
        field_name = field["name"]

        if field_name in row.index and pd.notna(row[field_name]):
            value = row[field_name]
            if isinstance(value, str) and value.strip().startswith(("[", "{")):
                try:
                    return json.loads(value)
                except json.JSONDecodeError:
                    logger.warning(f"Failed to parse JSON for '{field_name}', trying column-based parsing")

        custom_type_name = field["type"]
        parsed_custom_type = self.get_parsed_model_structure(custom_type_name)
        custom_type_fields = parsed_custom_type["fields"]

        return self.field_parser.build_custom_type_from_columns(row, custom_type_fields, custom_type_name)

    @staticmethod
    def to_camel_case(s: str) -> str:
        # Handle PascalCase
        s_with_spaces = re.sub(r"(?<!^)(?=[A-Z])", " ", s)

        parts = re.split(r"[\s_\-]+", s_with_spaces.strip())
        return parts[0].lower() + "".join(word.capitalize() for word in parts[1:])


def cmd_show(args=None):
    print(
        """
    ╔════════════════════════════════════════════════════╗
    ║        Amplify Migrator - Current Configuration    ║
    ╚════════════════════════════════════════════════════╝
    """
    )

    config_manager = ConfigManager()
    cached_config = config_manager.load()

    if not cached_config:
        print("\n❌ No configuration found!")
        print("💡 Run 'amplify-migrator config' first to set up your configuration.")
        return

    print("\n📋 Cached Configuration:")
    print("-" * 54)
    print(f"Excel file path:      {cached_config.get('excel_path', 'N/A')}")
    print(f"API endpoint:         {cached_config.get('api_endpoint', 'N/A')}")
    print(f"AWS Region:           {cached_config.get('region', 'N/A')}")
    print(f"User Pool ID:         {cached_config.get('user_pool_id', 'N/A')}")
    print(f"Client ID:            {cached_config.get('client_id', 'N/A')}")
    print(f"Admin Username:       {cached_config.get('username', 'N/A')}")
    print("-" * 54)
    print(f"\n📍 Config location: {config_manager.config_path}")
    print(f"💡 Run 'amplify-migrator config' to update configuration.")


def cmd_config(args=None):
    print(
        """
    ╔════════════════════════════════════════════════════╗
    ║        Amplify Migrator - Configuration Setup      ║
    ╚════════════════════════════════════════════════════╝
    """
    )

    config_manager = ConfigManager()
    cached_config = config_manager.load()

    config = {
        "excel_path": config_manager.prompt_for_value("Excel file path", cached_config.get("excel_path", "")),
        "api_endpoint": config_manager.prompt_for_value(
            "AWS Amplify API endpoint", cached_config.get("api_endpoint", "")
        ),
        "region": config_manager.prompt_for_value("AWS Region", cached_config.get("region", "")),
        "user_pool_id": config_manager.prompt_for_value("Cognito User Pool ID", cached_config.get("user_pool_id", "")),
        "client_id": config_manager.prompt_for_value("Cognito Client ID", cached_config.get("client_id", "")),
        "username": config_manager.prompt_for_value("Admin Username", cached_config.get("username", "")),
    }

    config_manager.save(config)
    print("\n✅ Configuration saved successfully!")
    print(f"💡 You can now run 'amplify-migrator migrate' to start the migration.")


def cmd_migrate(args=None):
    print(
        """
    ╔════════════════════════════════════════════════════╗
    ║             Migrator Tool for Amplify              ║
    ╠════════════════════════════════════════════════════╣
    ║   This tool requires admin privileges to execute   ║
    ╚════════════════════════════════════════════════════╝
    """
    )

    config_manager = ConfigManager()
    cached_config = config_manager.load()

    if not cached_config:
        print("\n❌ No configuration found!")
        print("💡 Run 'amplify-migrator config' first to set up your configuration.")
        sys.exit(1)

    excel_path = config_manager.get_or_prompt("excel_path", "Excel file path", "data.xlsx")
    api_endpoint = config_manager.get_or_prompt("api_endpoint", "AWS Amplify API endpoint")
    region = config_manager.get_or_prompt("region", "AWS Region", "us-east-1")
    user_pool_id = config_manager.get_or_prompt("user_pool_id", "Cognito User Pool ID")
    client_id = config_manager.get_or_prompt("client_id", "Cognito Client ID")
    username = config_manager.get_or_prompt("username", "Admin Username")

    print("\n🔐 Authentication:")
    print("-" * 54)
    password = config_manager.prompt_for_value("Admin Password", secret=True)

    from amplify_excel_migrator.auth import CognitoAuthProvider

    auth_provider = CognitoAuthProvider(
        user_pool_id=user_pool_id,
        client_id=client_id,
        region=region,
    )

    amplify_client = AmplifyClient(
        api_endpoint=api_endpoint,
        auth_provider=auth_provider,
    )

    if not auth_provider.authenticate(username, password):
        return

    excel_reader = ExcelReader(excel_path)
    field_parser = FieldParser()
    data_transformer = DataTransformer(field_parser)
    failure_tracker = FailureTracker()
    progress_reporter = ProgressReporter()
    batch_uploader = BatchUploader(amplify_client)

    orchestrator = MigrationOrchestrator(
        excel_reader=excel_reader,
        data_transformer=data_transformer,
        amplify_client=amplify_client,
        failure_tracker=failure_tracker,
        progress_reporter=progress_reporter,
        batch_uploader=batch_uploader,
        field_parser=field_parser,
    )

    orchestrator.run()


def main():
    parser = argparse.ArgumentParser(
        description="Amplify Excel Migrator - Migrate Excel data to AWS Amplify GraphQL API",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    config_parser = subparsers.add_parser("config", help="Configure the migration tool")
    config_parser.set_defaults(func=cmd_config)

    show_parser = subparsers.add_parser("show", help="Show current configuration")
    show_parser.set_defaults(func=cmd_show)

    migrate_parser = subparsers.add_parser("migrate", help="Run the migration")
    migrate_parser.set_defaults(func=cmd_migrate)

    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        sys.exit(1)

    args.func(args)


if __name__ == "__main__":
    # For IDE debugging: set the command you want to test
    # Uncomment and modify one of these lines:

    # sys.argv = ["migrator.py", "config"]  # Test config command
    # sys.argv = ['migrator.py', 'show']    # Test show command
    sys.argv = ["migrator.py", "migrate"]  # Test migrate command

    main()
