import sys
import os
import json
from datetime import datetime, timedelta
from collections import defaultdict
import statistics

# Přidání adresáře src do sys.path, aby bylo možné importovat moduly
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))

import database
import holidays

# Global debug flag
DEBUG = False


def debug_print(*args, **kwargs):
    """Print debug messages only if debug mode is enabled."""
    if DEBUG:
        print(*args, **kwargs)


def set_debug_mode(debug):
    """Set the debug mode for this module."""
    global DEBUG
    DEBUG = debug


def calculate_adaptive_threshold(currency_code, base_threshold=1.0, learning_months=3):
    """
    Calculates adaptive threshold based on 3-month historical volatility.

    :param currency_code: Currency to analyze
    :param base_threshold: Base threshold percentage
    :param learning_months: Months of history to analyze
    :return: Adaptive threshold and volatility statistics
    """
    try:
        # Calculate date range for learning (3 months back)
        end_date = datetime.now()
        start_date = end_date - timedelta(days=learning_months * 30)

        # Get all rates for the period
        rates_data = []
        current_date = start_date

        while current_date <= end_date:
            date_str = current_date.strftime("%d.%m.%Y")
            rate = database.get_rate(date_str, currency_code)
            if rate is not None:
                rates_data.append((current_date, rate))
            current_date += timedelta(days=1)

        if len(rates_data) < 10:
            # Insufficient data, return base threshold
            return {
                "adaptive_threshold": base_threshold,
                "base_threshold": base_threshold,
                "volatility_percent": 0.0,
                "data_points": len(rates_data),
                "sufficient_data": False,
            }

        # Calculate daily percentage changes
        changes = []
        for i in range(1, len(rates_data)):
            prev_rate = rates_data[i - 1][1]
            curr_rate = rates_data[i][1]
            if prev_rate > 0:
                change_pct = abs((curr_rate - prev_rate) / prev_rate) * 100
                changes.append(change_pct)

        if not changes:
            return {
                "adaptive_threshold": base_threshold,
                "base_threshold": base_threshold,
                "volatility_percent": 0.0,
                "data_points": len(rates_data),
                "sufficient_data": True,
            }

        # Calculate volatility metrics
        std_dev = statistics.stdev(changes)
        percentile_95 = statistics.quantiles(changes, n=20)[18]  # 95th percentile

        # Adaptive threshold formula: more conservative of std_dev and percentile_95th/2
        volatility_factor = max(std_dev, percentile_95 / 2)

        # Apply bounds (0.5% to 5.0%)
        adaptive_threshold = base_threshold * (
            1 + min(max(volatility_factor, 0.5), 5.0)
        )

        return {
            "adaptive_threshold": adaptive_threshold,
            "base_threshold": base_threshold,
            "volatility_percent": std_dev,
            "percentile_95": percentile_95,
            "data_points": len(rates_data),
            "sufficient_data": True,
        }

    except Exception as e:
        debug_print(f"Error calculating adaptive threshold: {e}")
        return {
            "adaptive_threshold": base_threshold,
            "base_threshold": base_threshold,
            "volatility_percent": 0.0,
            "data_points": 0,
            "sufficient_data": False,
            "error": str(e),
        }


def calculate_working_days_gap(start_date, end_date):
    """
    Calculate the number of working days (excluding weekends and holidays) between two dates.

    :param start_date: Start date (datetime)
    :param end_date: End date (datetime)
    :return: Number of working days between the dates (exclusive)
    """
    working_days = 0
    current = start_date + timedelta(days=1)  # Start from day after start_date

    while current < end_date:
        date_str = current.strftime("%d.%m.%Y")
        if not holidays.is_weekend(date_str) and not holidays.is_holiday(date_str):
            working_days += 1
        current += timedelta(days=1)

    return working_days


def calculate_expected_trading_days(year):
    """
    Calculate the expected number of trading days in a year (excluding weekends and holidays).

    :param year: Year to calculate for
    :return: Dictionary with expected trading days and breakdown
    """
    import calendar

    total_days = 366 if calendar.isleap(year) else 365
    weekend_days = 0
    holiday_days = 0

    # Count weekends and holidays
    for month in range(1, 13):
        for day in range(1, calendar.monthrange(year, month)[1] + 1):
            date_str = f"{day:02d}.{month:02d}.{year}"
            if holidays.is_weekend(date_str):
                weekend_days += 1
            elif holidays.is_holiday(date_str):
                holiday_days += 1

    expected_trading_days = total_days - weekend_days - holiday_days

    return {
        "total_days": total_days,
        "weekend_days": weekend_days,
        "holiday_days": holiday_days,
        "expected_trading_days": expected_trading_days,
    }


def validate_trading_days_count(currency_code, year):
    """
    Validate that a year has the appropriate number of trading day entries.

    :param currency_code: Currency to validate
    :param year: Year to check
    :return: Validation result with actual vs expected counts
    """
    # Get expected trading days
    expected = calculate_expected_trading_days(year)

    # Count actual data points for the year
    actual_count = 0
    rates_data = []

    start_date = datetime(year, 1, 1)
    end_date = datetime(year, 12, 31)

    current_date = start_date
    while current_date <= end_date:
        date_str = current_date.strftime("%d.%m.%Y")
        rate = database.get_rate(date_str, currency_code)
        if rate is not None:
            actual_count += 1
            rates_data.append((current_date, rate, date_str))
        current_date += timedelta(days=1)

    # Calculate discrepancy
    discrepancy_days = actual_count - expected["expected_trading_days"]
    discrepancy_percent = (
        (discrepancy_days / expected["expected_trading_days"]) * 100
        if expected["expected_trading_days"] > 0
        else 0
    )

    # Determine severity
    severity = "ok"
    if abs(discrepancy_percent) > 15:
        severity = "severe"
    elif abs(discrepancy_percent) > 5:
        severity = "moderate"
    elif abs(discrepancy_percent) > 0:
        severity = "minor"

    return {
        "expected_trading_days": expected["expected_trading_days"],
        "actual_data_points": actual_count,
        "discrepancy_days": discrepancy_days,
        "discrepancy_percent": round(discrepancy_percent, 2),
        "severity": severity,
        "total_days": expected["total_days"],
        "weekend_days_excluded": expected["weekend_days"],
        "holiday_days_excluded": expected["holiday_days"],
        "data_completeness_percent": round(
            (actual_count / expected["expected_trading_days"]) * 100, 1
        )
        if expected["expected_trading_days"] > 0
        else 0,
    }


def get_record_counts_by_period(currency_code, year=None):
    """
    Get record counts for different time periods.

    :param currency_code: Currency to analyze
    :param year: Optional year filter
    :return: Dictionary with counts by period
    """
    if year:
        years_to_check = [year]
    else:
        years_to_check = database.get_years_with_data()
        if not years_to_check:
            return {}

    results = {}

    for check_year in years_to_check:
        year_results = {}

        # Get all data for the year
        data_points = []
        start_date = datetime(check_year, 1, 1)
        end_date = datetime(check_year, 12, 31)

        current_date = start_date
        while current_date <= end_date:
            date_str = current_date.strftime("%d.%m.%Y")
            rate = database.get_rate(date_str, currency_code)
            if rate is not None:
                data_points.append((current_date, rate))
            current_date += timedelta(days=1)

        # Count by different periods
        period_counts = {
            "year": len(data_points),
            "half_year": {},
            "quarter": {},
            "month": {},
            "week": {},
        }

        # Half years
        period_counts["half_year"]["H1"] = len(
            [d for d in data_points if d[0].month <= 6]
        )
        period_counts["half_year"]["H2"] = len(
            [d for d in data_points if d[0].month > 6]
        )

        # Quarters
        for quarter in range(1, 5):
            start_month = (quarter - 1) * 3 + 1
            end_month = quarter * 3
            period_counts["quarter"][f"Q{quarter}"] = len(
                [d for d in data_points if start_month <= d[0].month <= end_month]
            )

        # Months
        for month in range(1, 13):
            period_counts["month"][f"{month:02d}"] = len(
                [d for d in data_points if d[0].month == month]
            )

        # Weeks (approximate by week number)
        week_counts = {}
        for data_point in data_points:
            week_num = data_point[0].isocalendar()[1]
            week_key = f"W{week_num:02d}"
            week_counts[week_key] = week_counts.get(week_key, 0) + 1
        period_counts["week"] = week_counts

        results[str(check_year)] = period_counts

    return results


def detect_temporal_gaps(currency_code, year=None, max_gap_days=3):
    """
    Detect temporal gaps in data sequence (missing working days).

    :param currency_code: Currency to validate
    :param year: Optional year filter
    :param max_gap_days: Maximum acceptable working days gap
    :return: List of gap violations
    """
    gaps = []

    try:
        # Get all dates and rates for the currency/year
        rates_data = []
        if year:
            # Specific year
            start_date = datetime(year, 1, 1)
            end_date = datetime(year, 12, 31)
        else:
            # All available data
            years_with_data = database.get_years_with_data()
            if not years_with_data:
                return gaps
            start_year = min(years_with_data)
            end_year = max(years_with_data)
            start_date = datetime(start_year, 1, 1)
            end_date = datetime(end_year, 12, 31)

        current_date = start_date
        while current_date <= datetime.now() and current_date <= end_date:
            date_str = current_date.strftime("%d.%m.%Y")
            rate = database.get_rate(date_str, currency_code)
            if rate is not None:
                rates_data.append((current_date, rate, date_str))
            current_date += timedelta(days=1)

        # Check for gaps between consecutive data points
        for i in range(1, len(rates_data)):
            prev_date, _, prev_date_str = rates_data[i - 1]
            curr_date, _, curr_date_str = rates_data[i]

            # Calculate working days gap
            working_days_gap = calculate_working_days_gap(prev_date, curr_date)

            if working_days_gap > max_gap_days:
                # Determine severity
                severity = "minor"
                if working_days_gap > max_gap_days * 3:
                    severity = "severe"
                elif working_days_gap > max_gap_days * 2:
                    severity = "moderate"

                gap = {
                    "start_date": prev_date_str,
                    "end_date": curr_date_str,
                    "working_days_missing": working_days_gap,
                    "severity": severity,
                    "max_expected_gap": max_gap_days,
                    "recommendation": f"Check data source for {working_days_gap} missing working days",
                }
                gaps.append(gap)

    except Exception as e:
        debug_print(f"Error detecting temporal gaps: {e}")

    return gaps


def detect_price_change_violations(
    currency_code, year=None, base_threshold=1.0, adaptive=True
):
    """
    Detects price changes exceeding thresholds.

    :param currency_code: Currency to validate
    :param year: Optional year filter
    :param base_threshold: Base threshold percentage
    :param adaptive: Whether to use adaptive threshold
    :return: List of violations
    """
    violations = []

    # Initialize adaptive_info in case of early exception
    adaptive_info = {
        "adaptive_threshold": base_threshold,
        "base_threshold": base_threshold,
        "volatility_percent": 0.0,
        "sufficient_data": True,
    }

    try:
        # Get adaptive threshold if enabled
        if adaptive:
            adaptive_info = calculate_adaptive_threshold(currency_code, base_threshold)

        effective_threshold = adaptive_info["adaptive_threshold"]

        # Get all dates and rates for the currency/year
        rates_data = []
        if year:
            # Specific year
            start_date = datetime(year, 1, 1)
            end_date = datetime(year, 12, 31)
        else:
            # All available data
            years_with_data = database.get_years_with_data()
            if not years_with_data:
                return violations, adaptive_info
            start_year = min(years_with_data)
            end_year = max(years_with_data)
            start_date = datetime(start_year, 1, 1)
            end_date = datetime(end_year, 12, 31)

        current_date = start_date
        while current_date <= datetime.now() and current_date <= end_date:
            date_str = current_date.strftime("%d.%m.%Y")
            rate = database.get_rate(date_str, currency_code)
            if rate is not None:
                rates_data.append((current_date, rate, date_str))
            current_date += timedelta(days=1)

        # Check consecutive pairs
        for i in range(1, len(rates_data)):
            prev_date, prev_rate, prev_date_str = rates_data[i - 1]
            curr_date, curr_rate, curr_date_str = rates_data[i]

            if prev_rate > 0:
                change_pct = abs((curr_rate - prev_rate) / prev_rate) * 100

                # Determine severity
                severity = "minor"
                if change_pct > effective_threshold * 3:
                    severity = "severe"
                elif change_pct > effective_threshold:
                    severity = "moderate"

                # Flag if exceeds base threshold (always) or adaptive threshold
                if change_pct > base_threshold:
                    violation = {
                        "date": curr_date_str,
                        "previous_date": prev_date_str,
                        "previous_rate": float(prev_rate),
                        "current_rate": float(curr_rate),
                        "change_percent": round(change_pct, 2),
                        "severity": severity,
                        "threshold_exceeded": "adaptive"
                        if change_pct > effective_threshold
                        else "base",
                        "effective_threshold": effective_threshold,
                    }

                    # Add corruption risk assessment for severe cases
                    if severity == "severe":
                        violation["corruption_risk"] = "high"
                        violation["recommendation"] = (
                            "Verify data source - potential currency mismatch or data corruption"
                        )

                    violations.append(violation)

    except Exception as e:
        debug_print(f"Error detecting price changes: {e}")

    return violations, adaptive_info


def validate_currency_data(
    currency_code, year=None, base_threshold=1.0, adaptive=True, max_gap_days=3
):
    """
    Comprehensive validation for a currency.

    :param currency_code: Currency to validate
    :param year: Optional year filter
    :param base_threshold: Base threshold for price changes
    :param adaptive: Whether to use adaptive thresholds
    :param max_gap_days: Maximum acceptable working days gap
    :return: Validation results
    """
    results = {
        "currency": currency_code,
        "validation_year": year,
        "validation_date": datetime.now().isoformat() + "Z",
    }

    try:
        # Price change violations
        violations, adaptive_info = detect_price_change_violations(
            currency_code, year, base_threshold, adaptive
        )

        # Temporal gaps
        gaps = detect_temporal_gaps(currency_code, year, max_gap_days)

        # Trading days validation
        trading_days_validation = None
        if year:
            trading_days_validation = validate_trading_days_count(currency_code, year)

        # Record counts by period
        record_counts = get_record_counts_by_period(currency_code, year)

        results["adaptive_analysis"] = adaptive_info
        results["price_change_violations"] = violations
        results["temporal_gaps"] = gaps
        results["trading_days_validation"] = trading_days_validation
        results["record_counts_by_period"] = record_counts

        # Summary statistics
        severity_counts = defaultdict(int)
        for v in violations:
            severity_counts[v["severity"]] += 1

        gap_severity_counts = defaultdict(int)
        for g in gaps:
            gap_severity_counts[g["severity"]] += 1

        results["summary"] = {
            "total_violations": len(violations),
            "total_gaps": len(gaps),
            "severity_breakdown": dict(severity_counts),
            "gap_severity_breakdown": dict(gap_severity_counts),
            "base_threshold": base_threshold,
            "adaptive_enabled": adaptive,
            "max_gap_days": max_gap_days,
        }

        # Data quality score (enhanced heuristic)
        quality_penalty = 0
        if violations:
            quality_penalty += (
                len(violations) * 5 + severity_counts.get("severe", 0) * 20
            )
        if gaps:
            quality_penalty += (
                len(gaps) * 10 + gap_severity_counts.get("severe", 0) * 30
            )
        if trading_days_validation and trading_days_validation["severity"] != "ok":
            severity_penalty = {"minor": 5, "moderate": 15, "severe": 30}
            quality_penalty += severity_penalty.get(
                trading_days_validation["severity"], 0
            )

        results["data_quality_score"] = max(0, 100 - quality_penalty)

    except Exception as e:
        results["error"] = str(e)
        results["data_quality_score"] = 0

    return results


def validate_all_currencies(
    year=None, base_threshold=1.0, adaptive=True, max_gap_days=3
):
    """
    Validates all available currencies.

    :param year: Optional year filter
    :param base_threshold: Base threshold for price changes
    :param adaptive: Whether to use adaptive thresholds
    :param max_gap_days: Maximum acceptable working days gap
    :return: Validation results for all currencies
    """
    results = {
        "validation_type": "all_currencies",
        "validation_year": year,
        "base_threshold": base_threshold,
        "adaptive_enabled": adaptive,
        "max_gap_days": max_gap_days,
        "validation_date": datetime.now().isoformat() + "Z",
        "currency_results": [],
    }

    try:
        # Get all available currencies (we'll check a few known ones and any in database)
        currencies_to_check = ["USD", "EUR", "GBP", "CHF", "JPY"]

        for currency in currencies_to_check:
            try:
                currency_result = validate_currency_data(
                    currency, year, base_threshold, adaptive, max_gap_days
                )
                results["currency_results"].append(currency_result)
            except Exception as e:
                results["currency_results"].append(
                    {"currency": currency, "error": str(e)}
                )

        # Overall summary
        total_violations = sum(
            r.get("summary", {}).get("total_violations", 0)
            for r in results["currency_results"]
            if "summary" in r
        )
        total_gaps = sum(
            r.get("summary", {}).get("total_gaps", 0)
            for r in results["currency_results"]
            if "summary" in r
        )
        severe_violations = sum(
            r.get("summary", {}).get("severity_breakdown", {}).get("severe", 0)
            for r in results["currency_results"]
            if "summary" in r
        )
        severe_gaps = sum(
            r.get("summary", {}).get("gap_severity_breakdown", {}).get("severe", 0)
            for r in results["currency_results"]
            if "summary" in r
        )

        results["overall_summary"] = {
            "currencies_checked": len(results["currency_results"]),
            "total_violations": total_violations,
            "total_gaps": total_gaps,
            "severe_violations": severe_violations,
            "severe_gaps": severe_gaps,
        }

    except Exception as e:
        results["error"] = str(e)

    return results


def format_validation_text(results):
    """Format validation results as text output."""
    output = []

    if "currency" in results:
        # Single currency validation
        output.append(
            f"Currency Validation: {results['currency']} ({results.get('validation_year', 'All Years')})"
        )
        output.append("=" * 60)

        adaptive = results.get("adaptive_analysis", {})
        if adaptive.get("sufficient_data", False):
            output.append("\nAdaptive Analysis (3-month history):")
            output.append(
                f"- Historical volatility: {adaptive.get('volatility_percent', 0):.1f}% std dev"
            )
            output.append(
                f"- Adaptive threshold: {adaptive.get('adaptive_threshold', 1.0):.1f}% (base: {adaptive.get('base_threshold', 1.0)}%)"
            )
            output.append(f"- Data points analyzed: {adaptive.get('data_points', 0)}")
        else:
            output.append(
                f"\nAdaptive Analysis: Insufficient data (using base threshold: {adaptive.get('base_threshold', 1.0)}%)"
            )

        violations = results.get("price_change_violations", [])
        if violations:
            output.append("\nPrice Change Violations:")
            for i, v in enumerate(violations, 1):
                severity = v["severity"].upper()
                output.append(
                    f"{i}. [{severity}] {v['date']}: {v['previous_rate']:.2f} → {v['current_rate']:.2f} ({'+' if v['change_percent'] > 0 else ''}{v['change_percent']:.2f}%)"
                )
                if "recommendation" in v:
                    output.append(f"   → {v['recommendation']}")
        else:
            output.append("\nPrice Change Violations: None found")

        gaps = results.get("temporal_gaps", [])
        if gaps:
            output.append("\nTemporal Gaps:")
            for i, g in enumerate(gaps, 1):
                severity = g["severity"].upper()
                output.append(
                    f"{i}. [{severity}] {g['start_date']} → {g['end_date']}: {g['working_days_missing']} working days missing"
                )
                if "recommendation" in g:
                    output.append(f"   → {g['recommendation']}")
        else:
            output.append("\nTemporal Gaps: None found")

        # Trading days validation
        trading_validation = results.get("trading_days_validation")
        if trading_validation:
            output.append("\nTrading Days Validation:")
            output.append(
                f"- Expected trading days: {trading_validation['expected_trading_days']} ({trading_validation.get('total_days', 'N/A')} total - {trading_validation.get('weekend_days_excluded', 0)} weekends - {trading_validation.get('holiday_days_excluded', 0)} holidays)"
            )
            output.append(
                f"- Actual data points: {trading_validation['actual_data_points']}"
            )
            output.append(
                f"- Discrepancy: {trading_validation['discrepancy_days']} days ({trading_validation['discrepancy_percent']}%)"
            )
            output.append(
                f"- Data completeness: {trading_validation['data_completeness_percent']}%"
            )
            output.append(f"- Status: {trading_validation['severity'].upper()}")

        # Record counts by period
        record_counts = results.get("record_counts_by_period", {})
        if record_counts:
            for year_key, periods in record_counts.items():
                output.append(f"\nRecord Counts for {year_key}:")
                output.append(f"- Year total: {periods.get('year', 0)} records")

                # Half years
                half_years = periods.get("half_year", {})
                if half_years:
                    output.append(
                        f"- Half years: H1={half_years.get('H1', 0)}, H2={half_years.get('H2', 0)}"
                    )

                # Quarters
                quarters = periods.get("quarter", {})
                if quarters:
                    quarter_str = ", ".join(
                        [f"Q{q}={quarters.get(f'Q{q}', 0)}" for q in range(1, 5)]
                    )
                    output.append(f"- Quarters: {quarter_str}")

                # Months summary
                months = periods.get("month", {})
                if months:
                    month_list = [
                        f"{m}={months.get(f'{int(m):02d}', 0)}"
                        for m in [
                            "01",
                            "02",
                            "03",
                            "04",
                            "05",
                            "06",
                            "07",
                            "08",
                            "09",
                            "10",
                            "11",
                            "12",
                        ]
                    ]
                    output.append(f"- Months: {', '.join(month_list)}")

                # Weeks summary (show first few and indicate total)
                weeks = periods.get("week", {})
                if weeks:
                    total_weeks = len(weeks)
                    if total_weeks <= 10:
                        week_list = [f"{w}={weeks[w]}" for w in sorted(weeks.keys())]
                        output.append(f"- Weeks: {', '.join(week_list)}")
                    else:
                        sample_weeks = sorted(list(weeks.keys())[:5])
                        week_sample = [f"{w}={weeks[w]}" for w in sample_weeks]
                        output.append(
                            f"- Weeks: {', '.join(week_sample)}... ({total_weeks} total weeks)"
                        )

        summary = results.get("summary", {})
        quality_score = results.get("data_quality_score", 0)
        output.append(f"\nData Quality Score: {quality_score}%")
        output.append(f"Total violations: {summary.get('total_violations', 0)}")
        output.append(f"Total gaps: {summary.get('total_gaps', 0)}")

    elif "currency_results" in results:
        # Multi-currency validation
        output.append("Multi-Currency Validation Report")
        output.append("=" * 60)

        for currency_result in results["currency_results"]:
            currency = currency_result.get("currency", "Unknown")
            violations = currency_result.get("price_change_violations", [])
            quality_score = currency_result.get("data_quality_score", 0)

            output.append(f"\n{currency}:")
            output.append(f"  - Violations: {len(violations)}")
            output.append(f"  - Quality Score: {quality_score}%")

            if violations:
                severe_count = sum(1 for v in violations if v["severity"] == "severe")
                output.append(f"  - Severe violations: {severe_count}")

        overall = results.get("overall_summary", {})
        output.append("\nOverall Summary:")
        output.append(f"- Currencies checked: {overall.get('currencies_checked', 0)}")
        output.append(f"- Total violations: {overall.get('total_violations', 0)}")
        output.append(f"- Severe violations: {overall.get('severe_violations', 0)}")

    return "\n".join(output)
