from django.shortcuts import render, redirect, get_object_or_404
import json
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.urls import reverse
from django.db import transaction
from django.utils import timezone

from .models import Domain, DomainTrafficDaily
from .forms import AddDomainForm, DomainSettingsForm
from core.goedge_client import GoEdgeClient
from core.models import SystemSettings, OperationLog
from plans.models import Plan
from core.utils import check_cname_map, bytes_to_gb


@login_required
def list_domains(request):
    domains = Domain.objects.filter(user=request.user).order_by('-created_at')
    # 计算当月用量与配额
    today = timezone.now().date()
    month_start = today.replace(day=1)
    sys = SystemSettings.objects.order_by('id').first()
    for d in domains:
        traffic_qs = DomainTrafficDaily.objects.filter(domain=d, day__gte=month_start)
        total_bytes = sum(t.bytes for t in traffic_qs)
        used_gb = bytes_to_gb(total_bytes)
        base_quota_gb = 0
        if d.current_plan and d.current_plan.included_traffic_gb_per_domain:
            base_quota_gb = d.current_plan.included_traffic_gb_per_domain
        elif sys:
            base_quota_gb = sys.default_free_traffic_gb_per_domain
        total_quota_gb = (base_quota_gb or 0) + (d.extra_free_traffic_gb_current_cycle or 0)
        # 动态属性用于模板展示
        d.used_gb = used_gb
        d.total_quota_gb = total_quota_gb
    return render(request, 'domains/list.html', {
        'domains': domains,
    })


@login_required
@transaction.atomic
def add_domain(request):
    if request.method == 'POST':
        form = AddDomainForm(request.POST)
        if form.is_valid():
            name = form.cleaned_data['name'].strip()
            subdomains = form.cleaned_data['subdomains']
            origin_host = form.cleaned_data['origin_host'].strip()
            origin_protocol = form.cleaned_data['origin_protocol']
            origin_port = form.cleaned_data['origin_port']
            plan = form.cleaned_data.get('plan')
            enable_websocket = form.cleaned_data.get('enable_websocket') or False

            # 构造域名列表：至少包含一个主域名或子域名
            domains_list = []
            if subdomains:
                domains_list.extend([f"{sub}.{name}" for sub in subdomains])
            else:
                domains_list.append(name)

            # 构造源站地址列表
            origin_addr = f"{origin_protocol}://{origin_host}:{origin_port}"
            origin_addrs = [origin_addr]

            # 初始化 GoEdge 客户端并创建服务
            try:
                client = GoEdgeClient()
                server_id = client.create_basic_http_server(
                    domains=domains_list,
                    origin_addrs=origin_addrs,
                    user_id=0,  # 管理员创建，不指定用户
                    enable_websocket=enable_websocket,
                )
            except Exception as e:
                messages.error(request, f'创建加速服务失败：{e}')
                return render(request, 'domains/add.html', {'form': form})

            # 保存域名记录
            sys = SystemSettings.objects.order_by('id').first()
            cname_template = (sys.cname_template if sys else '{sub}.cdn.example.com')
            cname_map = {}
            for sub in subdomains or []:
                cname_map[f"{sub}.{name}"] = cname_template.replace('{sub}', sub)
            if not cname_map:
                # 主域名CNAME场景（可选）
                cname_map[name] = cname_template.replace('{sub}', 'www')

            # 计费周期：设置为当前自然月
            today = timezone.now().date()
            month_start = today.replace(day=1)
            # 下个月第一天
            if month_start.month == 12:
                next_month_start = month_start.replace(year=month_start.year + 1, month=1, day=1)
            else:
                next_month_start = month_start.replace(month=month_start.month + 1, day=1)
            current_cycle_end = next_month_start - timezone.timedelta(days=1)

            domain = Domain.objects.create(
                user=request.user,
                name=name,
                status=Domain.STATUS_PENDING,
                current_plan=plan,
                current_cycle_start=month_start,
                current_cycle_end=current_cycle_end,
                cname_targets=cname_map,
                origin_config={
                    'host': origin_host,
                    'protocol': origin_protocol,
                    'port': origin_port,
                },
                edge_server_id=server_id,
            )

            # 记录操作日志
            try:
                OperationLog.objects.create(
                    actor=request.user,
                    action='create_domain',
                    target=name,
                    detail=f"server_id={server_id}; origin={origin_protocol}://{origin_host}:{origin_port}; subdomains={','.join(subdomains or [])}",
                )
            except Exception:
                # 日志写入失败不阻断主流程
                pass

            messages.success(request, '域名已创建，请按提示配置DNS CNAME记录并等待生效。')
            return redirect(reverse('domains:detail', kwargs={'domain_id': domain.id}))
    else:
        form = AddDomainForm()

    return render(request, 'domains/add.html', {'form': form})

@login_required
def domain_detail(request, domain_id: int):
    domain = get_object_or_404(Domain, id=domain_id, user=request.user)

    # 流量统计：当月（按自然月）
    today = timezone.now().date()
    month_start = today.replace(day=1)
    traffic_qs = DomainTrafficDaily.objects.filter(domain=domain, day__gte=month_start)
    total_bytes = sum(t.bytes for t in traffic_qs)
    used_gb = bytes_to_gb(total_bytes)

    # 配额计算：Plan 或系统默认 + 域名当期额外
    sys = SystemSettings.objects.order_by('id').first()
    base_quota_gb = 0
    if domain.current_plan and domain.current_plan.included_traffic_gb_per_domain:
        base_quota_gb = domain.current_plan.included_traffic_gb_per_domain
    elif sys:
        base_quota_gb = sys.default_free_traffic_gb_per_domain
    total_quota_gb = (base_quota_gb or 0) + (domain.extra_free_traffic_gb_current_cycle or 0)
    progress_pct = 0
    if total_quota_gb > 0:
        progress_pct = int(min(100, (used_gb / total_quota_gb) * 100))

    # 近7/30天统计与峰值（跨自然月）
    seven_start = today - timezone.timedelta(days=6)
    thirty_start = today - timezone.timedelta(days=29)
    last7_qs = DomainTrafficDaily.objects.filter(domain=domain, day__gte=seven_start, day__lte=today)
    last30_qs = DomainTrafficDaily.objects.filter(domain=domain, day__gte=thirty_start, day__lte=today)

    last7_total_gb = bytes_to_gb(sum(t.bytes for t in last7_qs))
    last30_total_gb = bytes_to_gb(sum(t.bytes for t in last30_qs))
    # 平均值（GB/日）
    last7_count = last7_qs.count()
    last30_count = last30_qs.count()
    last7_avg_gb = round((last7_total_gb / last7_count), 2) if last7_count else 0
    last30_avg_gb = round((last30_total_gb / last30_count), 2) if last30_count else 0

    peak_volume_record = last30_qs.order_by('-bytes').first()
    peak_bandwidth_record = last30_qs.order_by('-peak_bandwidth_mbps').first()
    peak_volume_day = None
    peak_bandwidth_day = None
    if peak_volume_record:
        peak_volume_day = {
            'day': peak_volume_record.day,
            'gb': bytes_to_gb(peak_volume_record.bytes),
        }
    if peak_bandwidth_record:
        peak_bandwidth_day = {
            'day': peak_bandwidth_record.day,
            'mbps': peak_bandwidth_record.peak_bandwidth_mbps,
        }

    # 近30天每日统计（GB 与峰值带宽）
    last_30 = list(last30_qs.order_by('-day'))
    traffic_rows = []
    for t in last_30:
        gb = bytes_to_gb(t.bytes)
        traffic_rows.append({'day': t.day, 'gb': gb, 'peak_mbps': t.peak_bandwidth_mbps})

    # GoEdge 同步状态
    goedge_status = None
    if domain.edge_server_id:
        try:
            client = GoEdgeClient()
            goedge_status = client.get_server_feature_status(domain.edge_server_id)
        except Exception as e:
            goedge_status = {'error': str(e)}

    requests_24h_total = None
    status_bins = None
    status_top = None
    if domain.edge_server_id:
        try:
            client = GoEdgeClient()
            hs = client.find_latest_server_hourly_stats(int(domain.edge_server_id), hours=24)
            requests_24h_total = sum(int(x.get('countRequests') or 0) for x in hs)
            agg = client.aggregate_status_codes(int(domain.edge_server_id))
            status_bins = agg.get('bins')
            status_top = agg.get('top')
        except Exception:
            pass

    context = {
        'domain': domain,
        'used_gb': used_gb,
        'total_quota_gb': total_quota_gb,
        'progress_pct': progress_pct,
        'traffic_rows': traffic_rows,
        'last7_total_gb': last7_total_gb,
        'last30_total_gb': last30_total_gb,
        'last7_avg_gb': last7_avg_gb,
        'last30_avg_gb': last30_avg_gb,
        'peak_volume_day': peak_volume_day,
        'peak_bandwidth_day': peak_bandwidth_day,
        'cname_results': None,
        'goedge_status': goedge_status,
        'requests_24h_total': requests_24h_total,
        'status_bins': status_bins,
        'status_top': status_top,
    }

    return render(request, 'domains/detail.html', context)


@login_required
@transaction.atomic
def check_dns(request, domain_id: int):
    domain = get_object_or_404(Domain, id=domain_id, user=request.user)
    if request.method != 'POST':
        return redirect(reverse('domains:detail', kwargs={'domain_id': domain.id}))

    cname_map = domain.cname_targets or {}
    results = check_cname_map(cname_map)

    ok_count = sum(1 for r in results if r.get('ok'))
    total = len(results)
    if ok_count == total and total > 0:
        messages.success(request, 'DNS 已生效，所有 CNAME 指向正确。')
        # 如果当前为等待DNS，可切换为 active
        if domain.status == Domain.STATUS_PENDING:
            domain.status = Domain.STATUS_ACTIVE
            domain.save(update_fields=['status'])
    else:
        messages.warning(request, f'DNS 检测完成：{ok_count}/{total} 条记录正确。请检查未生效的记录。')

    # 重渲染详情页并显示检测结果
    today = timezone.now().date()
    month_start = today.replace(day=1)
    traffic_qs = DomainTrafficDaily.objects.filter(domain=domain, day__gte=month_start)
    total_bytes = sum(t.bytes for t in traffic_qs)
    used_gb = bytes_to_gb(total_bytes)
    sys = SystemSettings.objects.order_by('id').first()
    base_quota_gb = 0
    if domain.current_plan and domain.current_plan.included_traffic_gb_per_domain:
        base_quota_gb = domain.current_plan.included_traffic_gb_per_domain
    elif sys:
        base_quota_gb = sys.default_free_traffic_gb_per_domain
    total_quota_gb = (base_quota_gb or 0) + (domain.extra_free_traffic_gb_current_cycle or 0)
    progress_pct = 0
    if total_quota_gb > 0:
        progress_pct = int(min(100, (used_gb / total_quota_gb) * 100))

    seven_start = today - timezone.timedelta(days=6)
    thirty_start = today - timezone.timedelta(days=29)
    last30_qs = DomainTrafficDaily.objects.filter(domain=domain, day__gte=thirty_start, day__lte=today)
    last7_qs = DomainTrafficDaily.objects.filter(domain=domain, day__gte=seven_start, day__lte=today)
    peak_volume_record = last30_qs.order_by('-bytes').first()
    peak_bandwidth_record = last30_qs.order_by('-peak_bandwidth_mbps').first()
    return render(request, 'domains/detail.html', {
        'domain': domain,
        'used_gb': used_gb,
        'total_quota_gb': total_quota_gb,
        'progress_pct': progress_pct,
        'traffic_rows': [{
            'day': t.day,
            'gb': bytes_to_gb(t.bytes),
            'peak_mbps': t.peak_bandwidth_mbps,
        } for t in last30_qs.order_by('-day')],
        'last7_total_gb': bytes_to_gb(sum(t.bytes for t in last7_qs)),
        'last30_total_gb': bytes_to_gb(sum(t.bytes for t in last30_qs)),
        'last7_avg_gb': (round(bytes_to_gb(sum(t.bytes for t in last7_qs)) / last7_qs.count(), 2) if last7_qs.count() else 0),
        'last30_avg_gb': (round(bytes_to_gb(sum(t.bytes for t in last30_qs)) / last30_qs.count(), 2) if last30_qs.count() else 0),
        'peak_volume_day': ({
            'day': peak_volume_record.day,
            'gb': bytes_to_gb(peak_volume_record.bytes),
        } if peak_volume_record else None),
        'peak_bandwidth_day': ({
            'day': peak_bandwidth_record.day,
            'mbps': peak_bandwidth_record.peak_bandwidth_mbps,
        } if peak_bandwidth_record else None),
        'cname_results': results,
    })

# Create your views here.


@login_required
@transaction.atomic
def upgrade_plan(request, domain_id: int):
    """最小套餐升级流程：展示公开套餐并允许用户选择变更当前域名的套餐。"""
    domain = get_object_or_404(Domain, id=domain_id, user=request.user)

    # 可选套餐列表（公开且激活）
    available_plans = Plan.objects.filter(is_active=True, is_public=True).order_by('base_price_per_domain')

    if request.method == 'POST':
        plan_id = request.POST.get('plan_id')
        try:
            new_plan = available_plans.get(id=plan_id)
        except Plan.DoesNotExist:
            messages.error(request, '选择的套餐不可用或不存在。')
            return render(request, 'domains/upgrade.html', {
                'domain': domain,
                'plans': available_plans,
            })

        old_plan = domain.current_plan
        domain.current_plan = new_plan
        domain.save(update_fields=['current_plan', 'updated_at'])

        # 操作日志
        try:
            OperationLog.objects.create(
                actor=request.user,
                action='upgrade_plan',
                target=domain.name,
                detail=f"from={old_plan.name if old_plan else '-'} to={new_plan.name}",
            )
        except Exception:
            pass

        messages.success(request, f'套餐已升级为：{new_plan.name}')
        return redirect(reverse('domains:detail', kwargs={'domain_id': domain.id}))

    return render(request, 'domains/upgrade.html', {
        'domain': domain,
        'plans': available_plans,
    })


@login_required
@transaction.atomic
def domain_settings(request, domain_id: int):
    """域名功能设置页：保存到 Domain.custom_features，后续接入 GoEdge 配置更新。"""
    domain = get_object_or_404(Domain, id=domain_id)
    # 权限：仅域名所有者或staff
    if not (domain.user_id == request.user.id or request.user.is_staff):
        messages.warning(request, '无权编辑该域名设置。')
        return redirect('domains:detail', domain_id=domain.id)

    plan_features = domain.current_plan.features if domain.current_plan else {}
    import json
    initial = {
        'waf_enabled': bool(domain.custom_features.get('waf_enabled', plan_features.get('waf_enabled', False))),
        'http3_enabled': bool(domain.custom_features.get('http3_enabled', plan_features.get('http3_enabled', False))),
        'logs_enabled': bool(domain.custom_features.get('logs_enabled', plan_features.get('logs_enabled', False))),
        'websocket_enabled': bool(domain.custom_features.get('websocket_enabled', plan_features.get('websocket_enabled', False))),
        'redirect_https_enabled': bool(domain.custom_features.get('redirect_https_enabled', False)),
        'cache_rules_json': json.dumps(domain.custom_features.get('cache_rules_json', {}), ensure_ascii=False),
        'page_rules_json': json.dumps(domain.custom_features.get('page_rules_json', {}), ensure_ascii=False),
        'ip_whitelist': ','.join(domain.custom_features.get('ip_whitelist', [])),
        'ip_blacklist': ','.join(domain.custom_features.get('ip_blacklist', [])),
    }

    if request.method == 'POST':
        form = DomainSettingsForm(request.POST)
        if form.is_valid():
            data = form.cleaned_data
            domain.custom_features = {
                'waf_enabled': bool(data.get('waf_enabled')),
                'http3_enabled': bool(data.get('http3_enabled')),
                'logs_enabled': bool(data.get('logs_enabled')),
                'websocket_enabled': bool(data.get('websocket_enabled')),
                'redirect_https_enabled': bool(data.get('redirect_https_enabled')),
                'cache_rules_json': data.get('cache_rules_json') or {},
                'page_rules_json': data.get('page_rules_json') or {},
                'ip_whitelist': data.get('ip_whitelist') or [],
                'ip_blacklist': data.get('ip_blacklist') or [],
            }
            domain.save(update_fields=['custom_features', 'updated_at'])
            # 记录操作日志（含策略ID与同步结果）
            log_detail = {
                'requested': {
                    'waf_enabled': bool(data.get('waf_enabled')),
                    'http3_enabled': bool(data.get('http3_enabled')),
                    'logs_enabled': bool(data.get('logs_enabled')),
                    'websocket_enabled': bool(data.get('websocket_enabled')),
                },
                'sync': {
                    'accessLog': None,
                    'websocket': None,
                    'firewall': None,
                    'ssl_http3': None,
                }
            }
            # 尝试同步到 GoEdge（根据可用策略与配置）
            try:
                client = GoEdgeClient()
                web_id = client.find_server_web_id(domain.edge_server_id)
                if web_id:
                    sys = SystemSettings.objects.order_by('id').first()
                    # 访问日志
                    client.update_http_web_access_log(
                        http_web_id=web_id,
                        is_on=bool(data.get('logs_enabled')),
                        policy_id=(sys.default_http_access_log_policy_id if sys else None)
                    )
                    log_detail['sync']['accessLog'] = {
                        'webId': web_id,
                        'isOn': bool(data.get('logs_enabled')),
                        'policyId': (sys.default_http_access_log_policy_id if sys else None)
                    }
                    # WebSocket
                    client.update_http_web_websocket(http_web_id=web_id, is_on=bool(data.get('websocket_enabled')))
                    log_detail['sync']['websocket'] = {'webId': web_id, 'isOn': bool(data.get('websocket_enabled'))}
                    # WAF（如有默认策略则引用）
                    client.update_http_web_firewall(
                        http_web_id=web_id,
                        is_on=bool(data.get('waf_enabled')),
                        policy_id=(sys.default_http_firewall_policy_id if sys else None)
                    )
                    log_detail['sync']['firewall'] = {
                        'webId': web_id,
                        'isOn': bool(data.get('waf_enabled')),
                        'policyId': (sys.default_http_firewall_policy_id if sys else None)
                    }
                    cache_conf = data.get('cache_rules_json') or {}
                    if isinstance(cache_conf, dict) and cache_conf:
                        client.update_http_web_cache(http_web_id=web_id, cache_conf=cache_conf)
                        log_detail['sync']['cache'] = {'webId': web_id, 'applied': True}
                    page_rules = data.get('page_rules_json') or {}
                    if isinstance(page_rules, dict) and page_rules:
                        locations_conf = page_rules.get('locations') or page_rules.get('Locations') or None
                        rewrite_conf = page_rules.get('rewriteRules') or page_rules.get('RewriteRules') or None
                        if locations_conf:
                            client.update_http_web_locations(http_web_id=web_id, locations_conf=locations_conf)
                            log_detail['sync']['locations'] = {'webId': web_id, 'count': len(locations_conf) if isinstance(locations_conf, list) else 1}
                        if rewrite_conf:
                            client.update_http_web_rewrite_rules(http_web_id=web_id, rewrite_conf=rewrite_conf)
                            log_detail['sync']['rewrite'] = {'webId': web_id, 'count': len(rewrite_conf) if isinstance(rewrite_conf, list) else 1}
                        # 强制 HTTPS 跳转
                        if bool(data.get('redirect_https_enabled')):
                            client.update_http_web_redirect_to_https(http_web_id=web_id, redirect_conf={'isOn': True})
                            log_detail['sync']['redirectToHTTPS'] = {'webId': web_id, 'isOn': True}
                        else:
                            client.update_http_web_redirect_to_https(http_web_id=web_id, redirect_conf={'isOn': False})
                            log_detail['sync']['redirectToHTTPS'] = {'webId': web_id, 'isOn': False}
                # HTTP/3 通过 SSL 策略更新
                ssl_policy_id = client.find_server_ssl_policy_id(domain.edge_server_id)
                if ssl_policy_id is not None:
                    client.update_ssl_policy_http3(ssl_policy_id, bool(data.get('http3_enabled')))
                    log_detail['sync']['ssl_http3'] = {'sslPolicyId': ssl_policy_id, 'http3Enabled': bool(data.get('http3_enabled'))}
            except Exception as e:
                messages.warning(request, f'部分功能未同步到 GoEdge：{e}')
                log_detail['sync']['error'] = str(e)
            # 写操作日志
            try:
                OperationLog.objects.create(
                    actor=request.user,
                    action='domain_settings_update',
                    target=domain.name,
                    detail=json.dumps(log_detail, ensure_ascii=False)
                )
            except Exception:
                pass
            messages.success(request, '设置已保存。部分功能需对应套餐支持方可生效。')
            return redirect('domains:settings', domain_id=domain.id)
        else:
            messages.error(request, '保存失败，请检查表单字段。')
    else:
        form = DomainSettingsForm(initial=initial)

    return render(request, 'domains/settings.html', {
        'domain': domain,
        'form': form,
        'plan_features': plan_features,
    })


@login_required
def domain_logs(request, domain_id: int):
    domain = get_object_or_404(Domain, id=domain_id, user=request.user)
    server_id = int(domain.edge_server_id or 0)
    logs = []
    request_id = request.GET.get('request_id') or None
    day = request.GET.get('day') or None
    hour_from = request.GET.get('hour_from') or None
    hour_to = request.GET.get('hour_to') or None
    ip = request.GET.get('ip') or None
    keyword = request.GET.get('keyword') or None
    size = int(request.GET.get('size') or 50)
    status_code = request.GET.get('status_code') or ''
    export = request.GET.get('export') or ''
    reverse = bool(request.GET.get('reverse'))
    has_more = False
    if server_id:
        try:
            client = GoEdgeClient()
            res = client.list_http_access_logs(
                server_id=server_id,
                day=day,
                size=size,
                hour_from=hour_from,
                hour_to=hour_to,
                reverse=reverse,
                ip=ip,
                keyword=keyword,
                request_id=request_id,
            )
            logs = res.get('logs') or []
            request_id = res.get('requestId') or None
            has_more = bool(res.get('hasMore'))
        except Exception as e:
            messages.warning(request, f'访问日志查询失败：{e}')
    # 过滤状态码
    if status_code:
        try:
            sc = int(status_code)
            logs = [l for l in (logs or []) if int(l.get('status') or 0) == sc]
        except Exception:
            pass
    # 导出
    if export == 'csv':
        import csv
        from django.http import HttpResponse
        resp = HttpResponse(content_type='text/csv; charset=utf-8')
        resp['Content-Disposition'] = f'attachment; filename="{domain.name}_access_logs.csv"'
        writer = csv.writer(resp)
        writer.writerow(['timeLocal', 'host', 'remoteAddr', 'method', 'requestURI', 'status', 'bytesSent', 'userAgent'])
        for l in logs:
            writer.writerow([
                l.get('timeLocal'), l.get('host'), l.get('remoteAddr'), l.get('method'), l.get('requestURI'), l.get('status'), l.get('bytesSent'), l.get('userAgent'),
            ])
        return resp
    if export == 'json':
        from django.http import JsonResponse
        return JsonResponse({'domain': domain.name, 'logs': logs}, json_dumps_params={'ensure_ascii': False})
    return render(request, 'domains/logs.html', {
        'domain': domain,
        'logs': logs,
        'day': day or '',
        'hour_from': hour_from or '',
        'hour_to': hour_to or '',
        'ip': ip or '',
        'keyword': keyword or '',
        'size': size,
        'reverse': reverse,
        'request_id': request_id or '',
        'has_more': has_more,
        'status_code': status_code or '',
    })
