using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Hc900Crawler.Core.Application.Interfaces;
using Hc900Crawler.Core.Application.DTOs;
using Hc900Crawler.Core.Domain.Entities;
using Microsoft.Extensions.Logging;

namespace Hc900Crawler.Web.Controllers;

[ApiController]
[Route("api/[controller]")]
public class PidController : ControllerBase
{
    private readonly IPidExtractorService _pidExtractor;
    private readonly ITagMappingService _tagMapping;
    private readonly ILogger<PidController> _logger;
    
    public PidController(IPidExtractorService pidExtractor, ITagMappingService tagMapping, ILogger<PidController> logger)
    {
        _pidExtractor = pidExtractor;
        _tagMapping = tagMapping;
        _logger = logger;
    }
    
    private static readonly string UploadDir =
        Path.Combine(Directory.GetCurrentDirectory(), "uploads", "pid");

    private static bool IsValidFileName(string name) =>
        !string.IsNullOrWhiteSpace(name) &&
        name == Path.GetFileName(name) &&
        (name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) ||
         name.EndsWith(".dxf", StringComparison.OrdinalIgnoreCase));

    [HttpPost("upload")]
    [DisableRequestSizeLimit]
    public async Task<IActionResult> UploadFile(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest(new { error = "파일이 없습니다." });

        var safeName = Path.GetFileName(file.FileName);
        if (!IsValidFileName(safeName))
            return BadRequest(new { error = "지원 형식: .dxf, .pdf" });

        Directory.CreateDirectory(UploadDir);
        var filePath = Path.Combine(UploadDir, safeName);

        await using var stream = System.IO.File.Create(filePath);
        await file.CopyToAsync(stream);

        _logger.LogInformation("[PID] 파일 저장 완료: {FileName} ({Bytes:N0} bytes)", safeName, file.Length);

        return Ok(new { fileName = safeName, fileSize = file.Length });
    }

    [HttpGet("server-files")]
    public IActionResult GetServerFiles()
    {
        if (!Directory.Exists(UploadDir))
            return Ok(new { files = Array.Empty<object>() });

        var files = Directory.GetFiles(UploadDir)
            .Where(f => IsValidFileName(Path.GetFileName(f)))
            .Select(f => new FileInfo(f))
            .OrderByDescending(f => f.LastWriteTimeUtc)
            .Select(f => new { fileName = f.Name, fileSize = f.Length, uploadedAt = f.LastWriteTimeUtc })
            .ToArray();

        return Ok(new { files });
    }

    [HttpPost("extract")]
    public async Task<IActionResult> Extract([FromBody] ExtractRequest request)
    {
        if (!IsValidFileName(request.FileName))
            return BadRequest(new { error = "파일명이 유효하지 않습니다." });

        var filePath = Path.Combine(UploadDir, request.FileName);
        if (!System.IO.File.Exists(filePath))
            return NotFound(new { error = $"서버에 파일이 없습니다: {request.FileName}" });

        _logger.LogInformation("[PID] 추출 시작: {FileName}, imageMode={ImageMode}", request.FileName, request.UseImageMode);
        var result = await _pidExtractor.ExtractFromFileAsync(filePath, request.UseImageMode);

        return Ok(new
        {
            totalCount = result.TotalCount,
            confidenceItems = result.ConfidenceItems,
            lowConfidenceItems = result.LowConfidenceItems,
            skippedDuplicates = result.SkippedDuplicates
        });
    }

    public record ExtractRequest(string FileName, bool UseImageMode = false);
    public record ConnectionsRequest(string FileName);

    [HttpPost("connections")]
    public async Task<IActionResult> AnalyzeConnections([FromBody] ConnectionsRequest request)
    {
        if (!IsValidFileName(request.FileName))
            return BadRequest(new { error = "파일명이 유효하지 않습니다." });

        _logger.LogInformation("[PID] 연결 분석 시작: {FileName}", request.FileName);
        var count = await _pidExtractor.AnalyzeConnectionsAsync(request.FileName);

        return Ok(new { connectionCount = count });
    }
    
    [HttpGet("equipment")]
    public async Task<IActionResult> GetEquipment(string? tagNo = null, string? category = null, int page = 1, int pageSize = 50, string? sortBy = null, bool sortDesc = false)
    {
        var (total, items) = await _pidExtractor.GetEquipmentAsync(tagNo, category, page, pageSize, sortBy, sortDesc);
        var itemList = items.ToList();
        
        var equipmentDtos = itemList.Select(e => new
        {
            id = e.Id,
            tagName = e.TagNo,
            equipmentName = e.EquipmentName,
            instrumentType = e.InstrumentType,
            lineNumber = e.LineNumber,
            pidDrawingNo = e.PidDrawingNo,
            confidence = e.Confidence,
            isActive = e.IsActive,
            extractedAt = e.ExtractedAt,
            updatedAt = e.UpdatedAt,
            experionTagId = e.ExperionTagId,
            experionTagName = e.ExperionTag?.TagName,
            category = e.Category,
            tagClass = e.TagClass,
            tagDcs = e.TagDcs,
            role = e.Role,
            fromTag = e.FromTag,
            fromAt = e.FromAt,
            toTag = e.ToTag,
            toAt = e.ToAt,
            subArea = e.SubArea,
            posX = e.PosX,
            posY = e.PosY,
            drawingFile = e.DrawingFile
        });
        
        return Ok(new
        {
            total,
            page,
            pageSize,
            confidenceRate = itemList.Count > 0 ? itemList.Average(e => e.Confidence) : 0,
            items = equipmentDtos
        });
    }

    [HttpPost("{id}/auto-map")]
    public async Task<IActionResult> AutoMap(long id)
    {
        var (found, tagName) = await _pidExtractor.AutoMapTagAsync(id);
        if (!found) return NotFound(new { error = "매칭되는 Experion 태그가 없습니다." });
        return Ok(new { tagName });
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateEquipment(long id, [FromBody] UpdateEquipmentRequest request)
    {
        var ok = await _pidExtractor.UpdateEquipmentAsync(id, request);
        if (!ok) return NotFound(new { error = "레코드를 찾을 수 없습니다." });
        return NoContent();
    }

    [HttpPost]
    public async Task<IActionResult> CreateEquipment([FromBody] CreateEquipmentRequest request)
    {
        var e = await _pidExtractor.CreateEquipmentAsync(request);
        return CreatedAtAction(nameof(CreateEquipment), new { id = e.Id }, new
        {
            id = e.Id,
            tagName = e.TagNo,
            equipmentName = e.EquipmentName,
            instrumentType = e.InstrumentType,
            category = e.Category,
            tagDcs = e.TagDcs,
            tagClass = e.TagClass,
            role = e.Role,
            fromTag = e.FromTag,
            toTag = e.ToTag
        });
    }

    [HttpGet("statistics")]
    public async Task<IActionResult> GetStatistics()
    {
        var confidenceItems = await _pidExtractor.GetConfidenceItemsCountAsync();
        var lowConfidenceItems = await _pidExtractor.GetLowConfidenceItemsCountAsync();
        var confidenceRange = await _pidExtractor.GetConfidenceDistributionAsync();
        var drawingCount = await _pidExtractor.GetDrawingCountAsync();
        var unmappedCount = await _tagMapping.GetUnmappedCountAsync();
        var mappedCount = await _tagMapping.GetMappedCountAsync();
        
        return Ok(new
        {
            confidenceItems,
            lowConfidenceItems,
            confidenceRange,
            drawingCount,
            unmappedCount,
            mappedCount
        });
    }
    
    [HttpPut("{id}/confidence")]
    public async Task<IActionResult> UpdateConfidence(long id, [FromBody] double confidence)
    {
        if (confidence < 0 || confidence > 1)
            return BadRequest(new { error = "신뢰도는 0~1 사이여야 합니다." });
        
        await _pidExtractor.UpdateConfidenceAsync(id, confidence);
        return NoContent();
    }
    
    [HttpPost("{id}/activate")]
    public async Task<IActionResult> Activate(long id)
    {
        await _pidExtractor.ActivateAsync(id);
        return NoContent();
    }
    
    [HttpPost("{id}/deactivate")]
    public async Task<IActionResult> Deactivate(long id)
    {
        await _pidExtractor.DeactivateAsync(id);
        return NoContent();
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(long id)
    {
        var ok = await _pidExtractor.DeleteAsync(id);
        if (!ok) return NotFound(new { error = "레코드를 찾을 수 없습니다." });
        return NoContent();
    }

    [HttpDelete("all")]
    public async Task<IActionResult> DeleteAll()
    {
        var count = await _pidExtractor.DeleteAllEquipmentAsync();
        return Ok(new { deletedCount = count, message = $"{count}건 삭제됨" });
    }
    
    [HttpGet("mappings")]
    public async Task<IActionResult> GetMappings(int page = 1, int pageSize = 50)
    {
        var (total, items) = await _tagMapping.GetMappingsAsync(page, pageSize);
        
        return Ok(new
        {
            total,
            page,
            pageSize,
            items = items
        });
    }
    
    [HttpPost("mappings")]
    public async Task<IActionResult> CreateMapping([FromBody] CreateMappingRequest request)
    {
        var result = await _tagMapping.CreateMappingAsync(request);
        return Ok(new { result = result });
    }
    
    [HttpPut("mappings/{id}")]
    public async Task<IActionResult> UpdateMapping(long id, [FromBody] UpdateMappingRequest request)
    {
        await _tagMapping.UpdateMappingAsync(id, request);
        return NoContent();
    }
    
    [HttpDelete("mappings/{id}")]
    public async Task<IActionResult> ClearMapping(long id)
    {
        await _tagMapping.ClearMappingAsync(id);
        return NoContent();
    }
    
    [HttpGet("mappings/unmapped")]
    public async Task<IActionResult> GetUnmappedCount()
    {
        var count = await _tagMapping.GetUnmappedCountAsync();
        return Ok(new { count });
    }
    
    [HttpGet("mappings/mapped")]
    public async Task<IActionResult> GetMappedCount()
    {
        var count = await _tagMapping.GetMappedCountAsync();
        return Ok(new { count });
    }
    
    [HttpGet("mappings/available-tags")]
    public async Task<IActionResult> GetAvailableTags()
    {
        var tags = await _tagMapping.GetAvailableTagsAsync();
        return Ok(new { tags });
    }

    // ── Prefix 규칙 CRUD ──────────────────────────────────────────────────

    [HttpGet("prefix-rules")]
    public async Task<IActionResult> GetPrefixRules()
    {
        var rules = await _pidExtractor.GetPrefixRulesAsync();
        return Ok(new
        {
            items = rules.Select(r => new
            {
                id = r.Id,
                prefix = r.Prefix,
                category = r.Category,
                tagDcs = r.TagDcs,
                description = r.Description,
                sortOrder = r.SortOrder,
                createdAt = r.CreatedAt
            })
        });
    }

    [HttpPost("prefix-rules")]
    public async Task<IActionResult> CreatePrefixRule([FromBody] CreatePidPrefixRuleRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Prefix))
            return BadRequest(new { error = "prefix는 필수입니다." });
        if (!PidEquipment.AllCategories.Contains(request.Category))
            return BadRequest(new { error = $"category는 {string.Join(", ", PidEquipment.AllCategories)} 중 하나여야 합니다." });

        var rule = await _pidExtractor.CreatePrefixRuleAsync(request);
        return Ok(new { id = rule.Id, prefix = rule.Prefix, category = rule.Category });
    }

    [HttpPut("prefix-rules/{id}")]
    public async Task<IActionResult> UpdatePrefixRule(int id, [FromBody] UpdatePidPrefixRuleRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Prefix))
            return BadRequest(new { error = "prefix는 필수입니다." });
        if (!PidEquipment.AllCategories.Contains(request.Category))
            return BadRequest(new { error = $"category는 {string.Join(", ", PidEquipment.AllCategories)} 중 하나여야 합니다." });

        var rule = await _pidExtractor.UpdatePrefixRuleAsync(id, request);
        if (rule == null) return NotFound(new { error = "규칙을 찾을 수 없습니다." });
        return Ok(new { id = rule.Id, prefix = rule.Prefix, category = rule.Category });
    }

    [HttpDelete("prefix-rules/{id}")]
    public async Task<IActionResult> DeletePrefixRule(int id)
    {
        var ok = await _pidExtractor.DeletePrefixRuleAsync(id);
        if (!ok) return NotFound(new { error = "규칙을 찾을 수 없습니다." });
        return NoContent();
    }

    [HttpPost("apply-categories")]
    public async Task<IActionResult> ApplyCategories()
    {
        var count = await _pidExtractor.ApplyCategoriesToExistingAsync();
        return Ok(new { applied = count });
    }

    /// <summary>편집한 export 엑셀(.xlsx)을 업로드하여 pid_equipment 를 UPSERT.</summary>
    [HttpPost("import-excel")]
    [DisableRequestSizeLimit]
    public async Task<IActionResult> ImportExcel(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest(new { error = "파일이 없습니다." });
        if (!file.FileName.EndsWith(".xlsx", StringComparison.OrdinalIgnoreCase))
            return BadRequest(new { error = "지원 형식: .xlsx (export 엑셀)" });

        try
        {
            await using var stream = file.OpenReadStream();
            var result = await _pidExtractor.ImportFromExcelAsync(stream);
            _logger.LogInformation(
                "[PID] 엑셀 import: {File} → {Upd}건 갱신, {Ins}건 신규, {Un}건 미매칭",
                file.FileName, result.RowsUpdated, result.RowsInserted, result.Unmatched);
            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[PID] 엑셀 import 실패: {File}", file.FileName);
            return BadRequest(new { error = $"import 실패: {ex.Message}" });
        }
    }
}
