from flask import Blueprint, request, jsonify, current_app
from flask_mail import Mail, Message
import os
from datetime import datetime
import smtplib
import html
import re
import base64
import uuid
import socket
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email import encoders
from email.header import Header
from email.utils import formataddr
from email.utils import encode_rfc2231
from urllib.parse import unquote_plus
from sqlalchemy import text
from src.models.db import db

# 이메일 검증 및 DNS 검사를 위한 라이브러리 (선택적)
try:
    from email_validator import validate_email as email_validator_validate, EmailNotValidError
    EMAIL_VALIDATOR_AVAILABLE = True
except ImportError:
    EMAIL_VALIDATOR_AVAILABLE = False

try:
    import dns.resolver
    DNS_AVAILABLE = True
except ImportError:
    DNS_AVAILABLE = False

feedback_bp = Blueprint('feedback', __name__)


def decode_query_like_value(value):
    """URL query 스타일로 전달된 문자열을 안전하게 디코딩(+ 처리 포함)."""
    if value is None:
        return ''
    decoded = str(value)
    for _ in range(2):
        try:
            next_value = unquote_plus(decoded)
            if next_value == decoded:
                break
            decoded = next_value
        except Exception:
            break
    return decoded


def _is_truthy(value):
    return str(value).strip().lower() in ('1', 'true', 'y', 'yes', 'on')


def update_userinf_email_by_userid_compno(user_id, compno, email):
    """
    userinf.USERID + COMPNO 기준으로 EMAIL 업데이트.
    Returns:
        (success: bool, updated_rows: int, error: str|None)
    """
    try:
        result = db.session.execute(
            text("""
                UPDATE mlinkdw.userinf
                SET EMAIL = :email
                WHERE USERID = :user_id
                  AND COMPNO = :compno
            """),
            {"email": email, "user_id": user_id, "compno": compno}
        )
        db.session.commit()
        return True, int(result.rowcount or 0), None
    except Exception as e:
        db.session.rollback()
        return False, 0, str(e)

# SMTP 설정
SMTP_SERVER = os.getenv('SMTP_SERVER', 'smtp.gmail.com')
SMTP_PORT = int(os.getenv('SMTP_PORT', '587'))
SMTP_USERNAME = os.getenv('SMTP_USERNAME')
SMTP_PASSWORD = os.getenv('SMTP_PASSWORD')
# 발신자 이메일 주소 (회사 발신 주소, 스팸 방지용)
SMTP_FROM_EMAIL = os.getenv('SMTP_FROM_EMAIL', 'noreply@sellmall.co.kr')

def validate_email_format(email):
    """
    1단계: 이메일 형식 검사 (email-validator 사용)
    
    Args:
        email: 검증할 이메일 주소
        
    Returns:
        tuple: (성공 여부, 오류 메시지 또는 정규화된 이메일)
    """
    if not email or not isinstance(email, str):
        return False, '이메일 주소가 입력되지 않았습니다.'
    
    # email-validator 사용 (RFC 5322 표준 준수)
    if EMAIL_VALIDATOR_AVAILABLE:
        try:
            # check_deliverability=False: DNS 검사는 별도로 수행
            valid = email_validator_validate(email, check_deliverability=False)
            return True, valid.email  # 정규화된 이메일 반환
        except EmailNotValidError as e:
            return False, str(e)
    else:
        # email-validator가 없으면 기본 정규식으로 폴백
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if re.match(pattern, email):
            return True, email
        else:
            return False, '이메일 형식이 올바르지 않습니다.'

def validate_email_domain(email):
    """
    2단계: 도메인 DNS 검사 (MX 레코드 또는 A 레코드 확인)
    
    Args:
        email: 검증할 이메일 주소
        
    Returns:
        tuple: (성공 여부, 오류 메시지)
    """
    if not email or '@' not in email:
        return False, '이메일 형식이 올바르지 않습니다.'
    
    domain = email.split('@')[1]
    
    # MX 레코드 확인 (우선)
    if DNS_AVAILABLE:
        try:
            mx_records = dns.resolver.resolve(domain, 'MX')
            if len(mx_records) > 0:
                return True, None
        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout):
            # MX 레코드가 없으면 A 레코드 확인
            try:
                a_records = dns.resolver.resolve(domain, 'A')
                if len(a_records) > 0:
                    return True, None
            except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout):
                return False, '도메인이 존재하지 않거나 이메일을 받을 수 없습니다.'
        except Exception as e:
            # DNS 조회 실패 시 기본 라이브러리로 폴백
            pass
    
    # 기본 라이브러리로 도메인 존재 여부 확인 (A 레코드)
    try:
        socket.gethostbyname(domain)
        return True, None
    except socket.gaierror:
        return False, '도메인이 존재하지 않습니다.'
    except Exception as e:
        return False, f'도메인 검증 중 오류가 발생했습니다: {str(e)}'

def validate_email(email):
    """
    이메일 주소 전체 검증 (형식 + DNS)
    
    Args:
        email: 검증할 이메일 주소
        
    Returns:
        tuple: (성공 여부, 오류 메시지)
    """
    # 1단계: 형식 검사 (email-validator 사용)
    format_valid, format_result = validate_email_format(email)
    if not format_valid:
        return False, format_result  # 오류 메시지 반환
    
    # format_result는 정규화된 이메일이거나 원본 이메일
    normalized_email = format_result if isinstance(format_result, str) else email
    
    # 2단계: DNS 검사
    success, error_msg = validate_email_domain(normalized_email)
    if not success:
        return False, error_msg or '도메인 검증에 실패했습니다.'
    
    return True, None

def extract_base64_images(html_content):
    """
    HTML에서 base64 이미지를 추출하여 파일로 변환
    
    Returns:
        tuple: (수정된 HTML, 이미지 파일 리스트)
    """
    images = []
    image_counter = 0
    
    # base64 이미지 패턴 찾기: <img src="data:image/[type];base64,[data]">
    # 또는 src='data:image/[type];base64,[data]'
    pattern = r'(<img[^>]*src=["\']?)data:image/([^;]+);base64,([^"\'>\s]+)(["\']?[^>]*>)'
    
    def replace_image(match):
        nonlocal image_counter
        prefix = match.group(1)  # <img ... src=" 또는 <img ... src='
        image_type = match.group(2)  # png, jpeg, gif 등
        base64_data = match.group(3)
        suffix = match.group(4)  # " 또는 ' 및 나머지 태그
        
        try:
            # base64 디코딩
            image_data = base64.b64decode(base64_data)
            
            # 이미지 파일 생성
            image_counter += 1
            filename = f'image_{image_counter}.{image_type}'
            cid = f'image_{image_counter}_{uuid.uuid4().hex[:8]}'
            
            images.append({
                'filename': filename,
                'data': image_data,
                'content_type': f'image/{image_type}',
                'cid': cid
            })
            
            # HTML에서 base64를 CID로 교체
            return f'{prefix}cid:{cid}{suffix}'
        except Exception as e:
            print(f"Base64 이미지 추출 실패: {e}")
            return match.group(0)  # 원본 유지
    
    modified_html = re.sub(pattern, replace_image, html_content)
    
    return modified_html, images

def send_feedback_email(feedback_data, attachments=None):
    """피드백을 관리자 이메일로 전송"""
    try:
        # 이메일 본문
        problem_type_labels = {
            'feature_request': '기능 요청',
            'bug_report': '문제 보고',
            'phone_verification': '전화번호 인증',
            'membership_payment': '회원 및 결제 문제',
            'account_issues': '계정 문제',
            'cannot_share': '공개적으로 공유할 수 없습니다',
            'other': '기타'
        }
        
        problem_type_raw = feedback_data.get('problemType', 'other')
        problem_type_label = problem_type_labels.get(problem_type_raw, problem_type_raw)

        # 이메일 메시지 생성 - multipart/mixed (본문 + 첨부파일)
        msg = MIMEMultipart('mixed')
        
        # 사용자 이메일과 관리자 이메일
        user_email = feedback_data.get('email', '')
        
        # 이메일 헤더 설정:
        # From: 회사 발신 주소 (스팸 방지)
        # To: 관리자 이메일
        # Cc: 사용자 이메일 (사용자도 사본을 받도록)
        # Reply-To: 사용자 이메일 (관리자가 답장하면 사용자에게 가도록)
        msg['From'] = formataddr((str(Header('M-Link 피드백 시스템', 'utf-8')), SMTP_FROM_EMAIL))
        msg['To'] = SMTP_USERNAME
        
        # Cc(참조)로 사용자 이메일 추가 (사용자도 이메일 사본을 받도록)
        if user_email:
            msg['Cc'] = user_email
            # Reply-To를 사용자 이메일로 설정 (관리자가 답장하면 사용자에게 가도록)
            msg['Reply-To'] = user_email
        
        # 제목을 UTF-8로 인코딩
        subject = f'[피드백] {problem_type_label} - {datetime.now().strftime("%Y-%m-%d %H:%M")}'
        msg['Subject'] = Header(subject, 'utf-8')
        
        # Bcc 미사용: 수신자는 To + Cc만 사용. Bcc 헤더가 있으면 제거
        if 'Bcc' in msg:
            del msg['Bcc']
        
        # 본문 부분 (multipart/alternative: 텍스트 + HTML)
        msg_body = MIMEMultipart('alternative')
        
        # HTML 본문 생성
        description = feedback_data.get('description', '')
        description_text = feedback_data.get('descriptionText', description)
        
        # HTML에서 base64 이미지 추출 및 변환
        description_with_cid, extracted_images = extract_base64_images(description)
        
        # HTML 이스케이프 처리 (XSS 방지)
        # description은 이미 HTML이므로 그대로 사용, description_text는 텍스트로 사용
        
        # HTML 이메일 본문
        html_body = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <style>
                body {{ font-family: Arial, sans-serif; line-height: 1.6; color: #333; }}
                .container {{ max-width: 800px; margin: 0 auto; padding: 20px; }}
                .header {{ background-color: #4CAF50; color: white; padding: 10px 20px; text-align: center; }}
                .header h1 {{ margin: 0; font-size: 1.2em; line-height: 1.2; }}
                .content {{ padding: 20px; background-color: #f9f9f9; }}
                .info-section {{ margin: 20px 0; padding: 15px; background-color: white; border-left: 4px solid #4CAF50; }}
                .info-label {{ font-weight: bold; color: #4CAF50; }}
                .description {{ margin-top: 10px; padding: 15px; background-color: white; border: 1px solid #ddd; }}
                .description-content {{ word-wrap: break-word; }}
                .footer {{ padding: 20px; text-align: center; color: #666; font-size: 12px; }}
            </style>
        </head>
        <body>
            <div class="container">
                <div class="header">
                    <h1>새로운 피드백이 접수되었습니다</h1>
                </div>
                <div class="content">
                    <div class="info-section">
                        <div><span class="info-label">문의 유형:</span> {problem_type_label}</div>
                    </div>
                    <div class="description">
                        <div class="info-label">문제 설명:</div>
                        <div class="description-content">{description_with_cid}</div>
                    </div>
                </div>
                <div class="footer">
                    <p>© 2024 M-Link. All rights reserved.</p>
                </div>
            </div>
        </body>
        </html>
        """
        
        # 텍스트 본문 (HTML을 지원하지 않는 클라이언트용)
        text_body = f"""
새로운 피드백이 접수되었습니다.

===== 사용자 정보 =====
문의유형: {problem_type_label}

===== 문제 설명 =====
{description_text}
        """
        
        # HTML과 텍스트 본문 모두 추가 (multipart/alternative)
        text_part = MIMEText(text_body, 'plain', 'utf-8')
        html_part = MIMEText(html_body, 'html', 'utf-8')
        
        # 텍스트를 먼저 추가 (클라이언트가 HTML을 지원하지 않을 경우 대체)
        msg_body.attach(text_part)
        msg_body.attach(html_part)
        
        # 본문을 메시지에 추가
        msg.attach(msg_body)
        
        # 추출된 base64 이미지를 인라인 첨부 파일로 추가
        for img in extracted_images:
            img_part = MIMEImage(img['data'], _subtype=img['content_type'].split('/')[1])
            img_part.add_header('Content-ID', f'<{img["cid"]}>')
            img_part.add_header('Content-Disposition', 'inline', filename=img['filename'])
            msg.attach(img_part)
        
        # 첨부 파일 처리
        if attachments:
            for i, attachment in enumerate(attachments):
                if attachment and attachment.filename:
                    part = MIMEBase('application', 'octet-stream')
                    part.set_payload(attachment.read())
                    encoders.encode_base64(part)
                    part.add_header(
                        'Content-Disposition',
                        'attachment',
                        filename=('utf-8', '', attachment.filename)
                    )
                    msg.attach(part)
        
        # SMTP 서버 연결 및 이메일 전송
        server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        server.starttls()
        server.login(SMTP_USERNAME, SMTP_PASSWORD)
        
        # 이메일 전송 (send_message를 사용하면 UTF-8 인코딩이 자동으로 처리됨)
        # From: 회사 발신 주소로 설정하여 스팸 필터 회피
        # To: 관리자, Cc: 사용자만 사용. Bcc 미사용.
        # to_addrs를 명시하면 수신자는 이 목록만 사용되며, 메시지의 Bcc 헤더는 무시됨.
        # 릴레이 로그에 Bcc로 표시되지 않도록 전송 직전에 Bcc 헤더 제거.
        if 'Bcc' in msg:
            del msg['Bcc']
        try:
            # 수신자 목록: 관리자(To) + 사용자(Cc) 만
            recipients = [SMTP_USERNAME]
            if user_email:
                recipients.append(user_email)
            refused = server.send_message(msg, from_addr=SMTP_USERNAME, to_addrs=recipients)
            # 일부 수신자 거부 시 dict 반환됨. 관리자 수신자 거부는 실패로 처리.
            if isinstance(refused, dict) and SMTP_USERNAME in refused:
                print(f"관리자 수신자 전달 실패: {SMTP_USERNAME}, reason={refused.get(SMTP_USERNAME)}")
                server.quit()
                return False
        except Exception as e:
            print(f"이메일 전송 오류: {e}")
            server.quit()
            return False
        
        server.quit()
        
        return True
        
    except Exception as e:
        print(f"이메일 전송 오류: {e}")
        return False

@feedback_bp.route('/api/feedback/submit', methods=['POST'])
def submit_feedback():
    """피드백 제출 처리"""
    try:
        # 요청 크기 로깅 (디버깅용)
        import logging
        logger = logging.getLogger(__name__)
        content_length = request.content_length or 0
        logger.info(f"피드백 요청 크기: {content_length} bytes ({content_length / (1024*1024):.2f} MB)")
        
        # 413 에러 처리 (요청 크기 초과) - 실제로 Flask에서 발생하는 경우에만 처리
        from werkzeug.exceptions import RequestEntityTooLarge
        try:
            # 요청 데이터 접근 시도 (크기 초과 시 예외 발생)
            _ = request.form
        except RequestEntityTooLarge as e:
            max_size = current_app.config.get('MAX_CONTENT_LENGTH', 'not set')
            max_size_mb = max_size / (1024*1024) if isinstance(max_size, int) else 'unknown'
            logger.error(f"Flask 요청 크기 초과: {content_length} bytes ({content_length / (1024*1024):.2f}MB), Flask MAX_CONTENT_LENGTH: {max_size_mb}MB")
            return jsonify({
                'error': f'요청 크기가 너무 큽니다. 파일 크기를 줄이거나 파일 개수를 줄여주세요. (현재: {content_length / (1024*1024):.2f}MB, 최대: {max_size_mb}MB)'
            }), 413
        except Exception as e:
            # 다른 예외는 상위로 전달
            logger.error(f"요청 데이터 접근 중 예외 발생: {type(e).__name__}: {str(e)}")
            raise
        # 폼 데이터 추출
        problem_type = request.form.get('problemType')
        description = request.form.get('description')
        email = request.form.get('email')
        source = decode_query_like_value(request.form.get('source', ''))
        support_user_id = decode_query_like_value(request.form.get('supportUserId', ''))
        support_compno_raw = decode_query_like_value(request.form.get('supportCompno', ''))
        support_user_name = decode_query_like_value(request.form.get('supportUserName', ''))
        support_user_email = decode_query_like_value(request.form.get('supportUserEmail', ''))
        update_member_email = _is_truthy(request.form.get('updateMemberEmail', 'false'))
        
        # 필수 필드 검증
        if not problem_type or not description or not email:
            return jsonify({'error': '모든 필수 필드를 입력해주세요.'}), 400
        
        # 이메일 검증 (1단계: 형식 검사, 2단계: DNS 검사)
        email_valid, email_error = validate_email(email)
        if not email_valid:
            return jsonify({'error': email_error or '이메일 주소가 올바르지 않습니다.'}), 400

        # (선택) 체크 시 EMAIL 업데이트
        # 단, /support 단독 진입처럼 식별 파라미터(user_id/compno)가 없으면
        # 업데이트만 건너뛰고 문의 메일 전송은 정상 진행.
        if update_member_email:
            if not support_user_id or not support_compno_raw:
                logger.info(
                    "회원 이메일 업데이트 스킵: user_id/compno 파라미터 없음 "
                    f"(USERID={support_user_id}, COMPNO={support_compno_raw})"
                )
            else:
                try:
                    support_compno = int(support_compno_raw)
                except ValueError:
                    logger.warning(f"회원 이메일 업데이트 스킵: compno 값 오류 ({support_compno_raw})")
                    support_compno = None

                if support_compno is not None:
                    if len(email) > 30:
                        return jsonify({'error': '이메일 길이가 너무 깁니다. (최대 30자)'}), 400

                    ok, updated_rows, update_error = update_userinf_email_by_userid_compno(
                        support_user_id, support_compno, email
                    )
                    if not ok:
                        return jsonify({'error': f'회원 정보 업데이트 실패: {update_error}'}), 500
                    if updated_rows == 0:
                        logger.warning(
                            "회원 이메일 업데이트 대상 없음: "
                            f"USERID={support_user_id}, COMPNO={support_compno}"
                        )
        
        # 첨부 파일 처리 (파일 크기 제한: 10MB per file)
        MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB
        MAX_TOTAL_SIZE = 30 * 1024 * 1024  # 총 30MB
        attachments = []
        total_size = 0
        
        for key in request.files:
            if key.startswith('attachment_'):
                file = request.files[key]
                if file and file.filename:
                    # 파일 크기 확인
                    file.seek(0, 2)  # 파일 끝으로 이동
                    file_size = file.tell()
                    file.seek(0)  # 파일 시작으로 복귀
                    
                    if file_size > MAX_FILE_SIZE:
                        return jsonify({
                            'error': f'파일 크기가 너무 큽니다. 각 파일은 최대 {MAX_FILE_SIZE // (1024*1024)}MB까지 업로드 가능합니다. ({file.filename})'
                        }), 400
                    
                    total_size += file_size
                    if total_size > MAX_TOTAL_SIZE:
                        return jsonify({
                            'error': f'전체 파일 크기가 너무 큽니다. 총 {MAX_TOTAL_SIZE // (1024*1024)}MB까지 업로드 가능합니다.'
                        }), 400
                    
                    attachments.append(file)
        
        # 피드백 데이터 구성
        feedback_data = {
            'problemType': problem_type,
            'description': description,
            'email': email,
            'source': source,
            'supportUserId': support_user_id,
            'supportCompno': support_compno_raw,
            'supportUserName': support_user_name,
            'supportUserEmail': support_user_email,
            'updateMemberEmail': update_member_email,
            'timestamp': datetime.now().isoformat()
        }
        
        # 관리자 이메일로 피드백 전송
        email_sent = send_feedback_email(feedback_data, attachments)
        
        if email_sent:
            return jsonify({
                'message': '피드백이 성공적으로 전송되었습니다.',
                'timestamp': feedback_data['timestamp']
            }), 200
        else:
            return jsonify({'error': '이메일 전송에 실패했습니다. 잠시 후 다시 시도해주세요.'}), 500
            
    except Exception as e:
        print(f"피드백 처리 오류: {e}")
        return jsonify({'error': '피드백 처리 중 오류가 발생했습니다.'}), 500

@feedback_bp.route('/api/feedback/health', methods=['GET'])
def feedback_health():
    """피드백 서비스 상태 확인"""
    return jsonify({
        'status': 'healthy',
        'smtp_configured': bool(SMTP_USERNAME and SMTP_PASSWORD),
        'timestamp': datetime.now().isoformat()
    }), 200 